| Binary Space Partitioning Tutorial Part II |
![]() | written by Gary Simmons Code by Gary Simmons & Adam Hoult Special Thanks to: Adam Hoult, Matthias Liertzer & Klaus Hartmanm |

| Are we infront of Node B? if Yes then Render Back Leaf Polygons and then Render Front Leaf Polygons else Render Front Leaf Polygons and then Render Back Leaf Polygons |



void DrawTree (long CameraLeaf)
{
for (int i=0;i<NumberOfLeafs;i++)
{
long offset=CameraLeaf*numberOfLeafs;
if (PVSData[offset+i]==1)
{
RenderLeaf(i);
}
}
}


void DrawTree(long leaf)
{
POLYGON *CurrentPoly;
int i;
long PVSOFFSET=LeafArray[leaf].PVSIndex;
BYTE *PVSPointer=PVSData;
PVSPointer+=PVSOFFSET;
long currentleaf=0;
while (currentleaf<NumberOfLeafs)
{
if (*PVSPointer!=0)
{
for (i=0;i<8;i++)
{
BYTE mask=1<<i;
BYTE pvs=*PVSPointer;
if (pvs&mask)
{
Render The Polygons in Leaf [ currentleaf ];
}
}// end for if pvsdata
currentleaf++;
}// end for i;
PVSPointer++;
}
else
{// we have hit a zero so read in the next byte and see how long the run of zeros is
PVSPointer++;
BYTE RunLength=*PVSPointer;
PVSPointer++;
currentleaf+=RunLength*8;
}
struct NODE
{
unsigned char IsLeaf;
unsigned long Plane;
unsigned long Front;
signed long Back;
BOUNDINGBOX BoundingBox;
};
Looking at the above structure you can see that there are no Front and
Back pointers but instead there are front and back "long"
variables.Instead of having pointers pointing to memory addresses that
hold the front and back nodes instead we simply store a long value that
is the index of another Node in the Node array.If Back is set to -1 then
it does not point to another node in the array but instead indicates
that there is solid space behind this Node (remembering that you never
have Back Leafs).If IsLeaf=0 then the Front value is the index of
another Node in the Node array however if IsLeaf=1 then this means that
to the front of this node is a Leaf.In this case "Front" does not hold
the value of an index of a node in the node array but instead holds the
index of a Leaf in the Leaf array.Also remember that we no longer use
the Polygons themselves as splitters but instead just the Planes of the
polygons are stored.Therefore the "Plane" variable in the Node structure
is an index in to the Plane Array which is an array where all the Nodes
Planes are stored.Please do no not worry about what the BoundingBox
variable is for we will look at this in a moment.The Plane structure
looks like this:-struct PLANE
{
D3DXVECTOR3 PointOnPlane;
D3DXVECTOR3 Normal;
} ;
The Plane structure just contains two Vectors that describe the plane
for the node.Node 1 in the array will use Plane 1 in the array and Node 2
in the Node array will use Plane 2 in the plane array etc.Figure G
below better decribes the memory organization we will be employing.

struct LEAF{
long StartPolygon;
long EndPolygon;
long PortalIndexList[50];
long NumberOfPortals;
long PVSIndex;
BOUNDINGBOX BoundingBox;
};
There will be a few variables in there that you do not understand yet
such as NumberOfPortals and PortalIndexList but we do not need those for
tree creation so will ignore them for now.They will be used later to
calculate the PVS set. "PVSIndex" will eventually hold the position in
to the Master PVS array where this leafs visibility information
begins.That will all become clearer later.For now we are only interested
in StartPolygon and EndPolygon. As with the Node based compiler we
developed in Part 1 we will pass in our polygon data into the Compiler
as a linked list of polygons just like before.The difference is that
once we find we have a list of polygons that are convex (they have all
been used as splitters) we can delete it in the linked list and copy its
memory in to a Master Polygon array.The great thing about this is that
because a list of polygons in a Nodes Front list will only get copied
into the Polygon array when they form a leaf it means the polygons get
written into the array Leaf at a time.This means Leaf[0]'s polygons all
follow each other in the array followed by Leaf[1]'s polygons etc. This
means all we have to store in the Leaf structure is where in the array
it's polygons start and end. Actually 'EndPolygon' as you can see from
the diagram is the first polygon in the Next Leaf. This is because I
render them in a loop using " For
(poly=Leaf[n].StartPolygon;poly<Leaf.[n].EndPolygon;poly++)". In
other words we render from Start to End-1 but thats just standard c++
loop stuff.


void InitPolygons(void)
{
ReserveInitialMemoryForArrays();
PolygonList=NULL;
PolygonList=LoadMWM("demolevel.mwm");
LoadTextures();
BuildBspTree(0,PolygonList);
...
...
...
}
The InitPolygons function is called just after the D3D environment has
been set up.The first thing it must do is call
'ReserveInitialMemoryForArrays' function which is in MemAlloc.Cpp.We
will see this function in a moment after we have just had a look at the
net couple of lines.The Next 'InitPolygons' does is sets the PolygonList
pointer to NULL for safety.The PolygonList pointer is a global pointer
that will point to the head of a linked list of polygons that will be
fed into the compiler.This is virtually exactly the same as the previous
tutorial except the Polygon Structure is a little different.Here is the
Polygon Structure.struct POLYGON
{
D3DLVERTEX *VertexList;
D3DXVECTOR3 Normal;
WORD NumberOfVertices;
WORD NumberOfIndices;
WORD *Indices;
POLYGON * Next;
bool BeenUsedAsSplitter;
long TextureIndex;
};
This is almost identicel to the last tutorial.The Polygons will be
linked together by the 'Next' pointer and the Polygon itself will be
made from an indexed triangle list.The 'AddPolygon' function we will
look at in a moment is responsable for taking an n sided polygon and
breaking it down into indexed triangle.This was covered in detail in the
last tutorial.Each polygon also has a texture index.In the demo I load
in 25 textures into an array of IDirect3dTexture8 objects so this is
basically just an index in to this array describing what texture the
polygon uses.Also note the 'BeenUsedasSplitter' flag that is set during
compilation of the bsp tree to signify that this polygon has already
been used so can not be used as a split plane again.| API Notice for DX8: With DX8 the D3DLVERTEX built in vertex structure is no longer present.You now specify vertex types using the flexible vertex format.I have named my Custom Vertex structure 'D3DLVERTEX' after the structure that it emulates from Dx7.It is defined in 'pvs.h' as follows:- struct D3DLVERTEX
The Flexible Vertex Format Flags for this vertex are as follows:->#define D3DFVF_LVERTEX ( D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_SPECULAR | D3DFVF_TEX1 ) |
PolygonList=LoadMWM("demolevel.mwm");
long MAXNUMBEROFNODES =100;
long MAXNUMBEROFPOLYGONS=100;
long MAXNUMBEROFPLANES =100;
long MAXNUMBEROFLEAFS =100;
long MAXNUMBEROFPORTALS =100;
POLYGON *PolygonArray;
NODE *NodeArray;
LEAF *LeafArray;
PLANE *PlaneArray;
PORTAL **PortalArray;
BYTE *PVSData;
Do not worry about the PortalArray for now this is something we will
discuss way on down the tutorial.The last few globals to be declared in
memalloc.cpp are the actual variables to keep track of how many
Planes,Nodes,Polygons etc we have in our array.Remember that how many
NOdes we have in the Node Array is not the same as MAXNUMBEROFNODES as
this contains how many nodes we can store in our array before we need to
resize.In other words if we have just added our 101st Node to the node
array then the Number Of Nodes will be 101 but the MAXNUMBEROFNODES will
be 200 as it is resized on the 100 boundry.long NumberOfPolygons=0;
long NumberOfNodes=0;
long NumberOfLeafs=0;
long NumberOfPlanes=0;
long NumberOfPortals=0;
Once again do not worry about NumberOfPortals yet.OK now we know what
the Global arrays look like lets have a look at that
'ReserveInitialMemoryForArrays' function.void ReserveInitialMemoryForArrays()
{
NodeArray= (NODE *) malloc (MAXNUMBEROFNODES *sizeof(NODE));
PolygonArray=(POLYGON *)malloc (MAXNUMBEROFPOLYGONS *sizeof(POLYGON));
PlaneArray =(PLANE *) malloc (MAXNUMBEROFPLANES *sizeof(PLANE));
LeafArray =(LEAF *) malloc (MAXNUMBEROFLEAFS *sizeof(LEAF));
PortalArray =(PORTAL**) malloc (MAXNUMBEROFPORTALS *sizeof(PORTAL *));
ZeroMemory(NodeArray, sizeof(NODE) *MAXNUMBEROFNODES);
ZeroMemory(LeafArray, sizeof(LEAF) *MAXNUMBEROFLEAFS);
ZeroMemory(PlaneArray, sizeof(PLANE) *MAXNUMBEROFPLANES);
ZeroMemory(PolygonArray,sizeof(POLYGON) *MAXNUMBEROFPOLYGONS);
ZeroMemory(PortalArray, sizeof(PORTAL*) *MAXNUMBEROFPORTALS);
}
Nothing to explain here.Each array is dynamically initiated as being
large enough to hold 100 elements of their type.Then the arrays are
initialized to zero for safety.So we now have a Node Array, and Plane
Array a Leaf Array and a Polygon Array each large enough to hold 100
elements but for the moment are empty.PolygonList=LoadMWM("demolevel.mwm");
| WORD | Number of Polygons in File |
| WORD | Number of Vertices in Polygon N |
| D3DLVERTEX | Vertex Number M |
| WORD | Texture Index for Polygon N |
POLYGON * LoadMWM(char *filename)
{
FILE *stream;
POLYGON *Root=NULL;
POLYGON * Child=NULL;
WORD PolyCount,numofverts;
int i,b;
D3DLVERTEX xyzBuffer[50];
WORD TextureIndex;
DWORD uselessinfo;
stream = fopen(filename, "rb");
fread(&PolyCount, sizeof( WORD ), 1, stream );
for (i=0;i<PolyCount;i++)
{
fread(&numofverts,sizeof(WORD),1,stream);
for (b=0;b<numofverts;b++)
{
fread(&xyzBuffer[b].x, sizeof(float),1,stream);
fread(&xyzBuffer[b].y, sizeof(float),1,stream);
fread(&xyzBuffer[b].z, sizeof(float),1,stream);
fread(&uselessinfo, sizeof(DWORD),1,stream );
fread(&xyzBuffer[b].color, sizeof(D3DCOLOR),1,stream);
fread(&xyzBuffer[b].specular,sizeof(D3DCOLOR),1,stream);
fread(&xyzBuffer[b].tu, sizeof(float),1,stream);
fread(&xyzBuffer[b].tv, sizeof(float),1,stream);
}
fread(&TextureIndex,sizeof(WORD),1,stream);
if (i==0)
{
Root=AddPolygon(NULL,&xyzBuffer[0],numofverts);
Child=Root;
}
else
{
Child=AddPolygon(Child,&xyzBuffer[0],numofverts);
}
Child->TextureIndex=TextureIndex;
}
fclose (stream);
return Root;
}
As you can see there is nothing fancy about the file extraction.We
simply read in the Number of polygons in the file and then do a for
next/next loop for each polygon.For each polygon we read in its number
of vertices and then the vertex list which is just a bunch of D3DLVERTEX
structures. Notice how I load in one DWORD of useless information.This
is because my Polygon Editor was written in DX7 where the built in
D3DLVERTEX structure had a reserved DWORD value after the first vector.
The D3DLVERTEX is no longer internal to DX8 so I have emulated it using
the Flexible Vertex Format. Our Custom D3DLVERTEX structure though does
not have that Reserved DWORD so we simply read it in and discard it just
as a simple means of moving the file read counter on to the next useful
bit of information.
POLYGON * AddPolygon(POLYGON* Parent,D3DLVERTEX *Vertices,WORD NOV)
{
int loop;
POLYGON * Child=new POLYGON;
Child->NumberOfVertices=NOV;
Child->NumberOfIndices=(NOV-2)*3;
Child->Next=NULL;
Child->BeenUsedAsSplitter=false;
// Reserve space for Vertex and Index Lists
Child->VertexList =new D3DLVERTEX [Child->NumberOfVertices];
Child->Indices =new WORD [Child->NumberOfIndices];
for (loop=0;loop<NOV;loop++)
{
Child->VertexList[loop]=Vertices[loop];
}
//calculate indices
WORD v0,v1,v2;
for (loop=0;loop<Child->NumberOfIndices/3;loop++)
{
if (loop==0)
{
v0=0;
v1=1;
v2=2;
}
else
{
v1=v2;
v2++;
}
Child->Indices[loop*3]=v0;
Child->Indices[(loop*3)+1]=v1;
Child->Indices[(loop*3)+2]=v2;
}
// generate polygon normal
D3DXVECTOR3 * vec0=(D3DXVECTOR3 *) &Child->VertexList[0];
D3DXVECTOR3 * vec1=(D3DXVECTOR3 *) &Child->VertexList[1];
D3DXVECTOR3 * vec2=(D3DXVECTOR3 *) &Child->VertexList[Child->NumberOfVertices-1];// the last vert
D3DXVECTOR3 edge1=(*vec1)-(*vec0);
D3DXVECTOR3 edge2=(*vec2)-(*vec0);
D3DCVec3Cross(&Child->Normal,&edge1,&edge2) // child->normal=CrossProduct of edge1/edge2
D3DXVec3Normalize(&Child->Normal,&Child->Normal);// and then Normalize
if (Parent!=NULL)
{
Parent->Next=Child;
}
return Child;
}
If you have read the previous tutorial (and I really hope you have) then
this code will be instantly familiar to you. The only difference here
is that I now dynamically allocate the memory for both the Vertex and
Index lists. Like I said before this code was explained in detail in
Part 1 of this tutorial.| API Notice for DX8/D3DX: For those of you not familiar with the D3DX functions that have now become almost an integral part if DX8 (replacing most of the framework) some of the above function may be a little strange to you.I have used them throughout this tutorial to calulate the CrossProduct,DotProduct and for Normalizing Vectors.The functions to do this are as follows:- CrossProduct(v1,v2)=D3DXVec3Cross(&result,&v1,&v2); DotProduct(v1,v2)=D3DXVec3Dot(&v1,&v2); Normalize(v1)=D3DXVec3Normalize(&result,&v1); |
void InitPolygons(void)
{
ReserveInitialMemoryForArrays();
PolygonList=NULL;
PolygonList=LoadMWM("demolevel.mwm");
LoadTextures();
BuildBspTree(0,PolygonList);
...
...
...
}
You can see that the next line of code to be executed is called
LoadTextures and there are no prizes for guessing what this function
does. We have a global array of Direct3DTexture8 surface pointers
defined like so:-LPDIRECT3DTEXTURE8 lpTextureSurface[25];void LoadTextures(void)
{
D3DXCreateTextureFromFileA(lpDevice,"checkered_floor1.jpg",&lpTextureSurface[0]);
D3DXCreateTextureFromFileA(lpDevice,"brick2.jpg", &lpTextureSurface[1]);
D3DXCreateTextureFromFileA(lpDevice,"metalrustyfloor1.jpg",&lpTextureSurface[2]);
D3DXCreateTextureFromFileA(lpDevice,"brick3.jpg", &lpTextureSurface[3]);
D3DXCreateTextureFromFileA(lpDevice,"curvyfloor.jpg", &lpTextureSurface[4]);
D3DXCreateTextureFromFileA(lpDevice,"doomfloor.jpg", &lpTextureSurface[5]);
D3DXCreateTextureFromFileA(lpDevice,"crate.jpg", &lpTextureSurface[6]);
D3DXCreateTextureFromFileA(lpDevice,"stones1.jpg", &lpTextureSurface[7]);
D3DXCreateTextureFromFileA(lpDevice,"wood1.jpg", &lpTextureSurface[8]);
D3DXCreateTextureFromFileA(lpDevice,"wood2.jpg", &lpTextureSurface[9]);
D3DXCreateTextureFromFileA(lpDevice,"celtic.jpg", &lpTextureSurface[10]);
D3DXCreateTextureFromFileA(lpDevice,"celtic1.jpg", &lpTextureSurface[11]);
D3DXCreateTextureFromFileA(lpDevice,"rock1.jpg", &lpTextureSurface[12]);
D3DXCreateTextureFromFileA(lpDevice,"oldmetalriveted.jpg", &lpTextureSurface[13]);
D3DXCreateTextureFromFileA(lpDevice,"stone2.jpg", &lpTextureSurface[14]);
D3DXCreateTextureFromFileA(lpDevice,"brick1.jpg", &lpTextureSurface[15]);
D3DXCreateTextureFromFileA(lpDevice,"concrete1.jpg", &lpTextureSurface[16]);
D3DXCreateTextureFromFileA(lpDevice,"brickz2.jpg", &lpTextureSurface[17]);
D3DXCreateTextureFromFileA(lpDevice,"construct2.jpg", &lpTextureSurface[18]);
D3DXCreateTextureFromFileA(lpDevice,"construct2c.jpg", &lpTextureSurface[19]);
D3DXCreateTextureFromFileA(lpDevice,"doomgrey1.jpg", &lpTextureSurface[20]);
D3DXCreateTextureFromFileA(lpDevice,"doomgrey2.jpg", &lpTextureSurface[21]);
D3DXCreateTextureFromFileA(lpDevice,"granitefloor.jpg", &lpTextureSurface[22]);
D3DXCreateTextureFromFileA(lpDevice,"stained_glass1.jpg", &lpTextureSurface[23]);
D3DXCreateTextureFromFileA(lpDevice,"stained_glass2.jpg", &lpTextureSurface[24]);
}
Now, we have everything set up.Textures Loaded,memory allocated for the
initial arrays and a POLYGON * (PolygonList) that points to the root of a
linked polygon list of every polygon in our level.| I would just like to say something here that may be confusing you.We have a linked list of POLYGON structures and we also have an Array of 100 POLYGON structures.At the moment the array is totally blank. When our BSP compiler finds a convex leaf as its front list it will copy those polygons into the PolygonArray (resizing the array if needed) and will then delete the original polygons from the linked list to free up their memory because they are no longer needed. You can think of the Linked list as the unsorted data and as our BSP compiler finds the polygon that make up convex leafs they will be removed from the list and copied over to the PolygonArray ordered by the leaf they are in. In other words we never have two duplicate sets of polygons in memory at the same time.Polygons are added to the Array as they are removed from the linked list. |
BuildBspTree(0,PolygonList);
void BuildBspTree(long Node,POLYGON * PolyList)
{
POLYGON *polyTest=NULL;
POLYGON *FrontList=NULL;
POLYGON *BackList=NULL;
POLYGON *NextPolygon=NULL;
POLYGON *FrontSplit=NULL;
POLYGON *BackSplit=NULL;
D3DXVECTOR3 vec1,vec2;
D3DXVECTOR3 a,b;
float result;
NodeArray[Node].Plane=SelectBestSplitter(PolyList);
polyTest=PolyList;
// set up dummy bounding boxes for the node so it can be altered
NodeArray[Node].BoundingBox.BoxMax=D3DXVECTOR3(-40000,-40000,-40000);
NodeArray[Node].BoundingBox.BoxMin=D3DXVECTOR3(40000,40000,40000);
First we set up some local variables to hold the front and back lists
etc just like in the previous tutorial and then we call the
SelectBestSplitter function to choose a polygon from the list to use as a
split plane.Once again the code to this function is nearly identicle to
the previous tutorial but we will look at it in detail a little later
because there are a few differences.They are:-
while (polyTest!=NULL )
{
NextPolygon=polyTest->Next;// remember because polytest->Next will be altered
switch (ClassifyPoly(&PlaneArray[NodeArray[Node].Plane],polyTest))
{
case CP_ONPLANE:
a=PlaneArray[NodeArray[Node].Plane].Normal;
b=polyTest->Normal;
result=(float)fabs ((a.x-b.x)+(a.y-b.y)+(a.z-b.z));
if (result<0.1)
{
polyTest->Next=FrontList;
FrontList=polyTest;
}
else
{
polyTest->Next=BackList;
BackList=polyTest;
}
break;
First we initiate a 'while' loop that will continue on through the
linked list of polygons until there are no more left.This loop will be
the what takes all the polygons passed into the function and will group
them in two Linked lists (FrontLists & BackLists) depending on their
relation to the split plane.In other words when this loop exits we will
have two more linked lists Front and Back containing the polygons that
lay in that sub space. Then we make a backup copy of the Current
Polygons 'Next' pointer.This is because the current polygon will be
added to either the Front or Back list using its next pointer whichl
means we will loose any way of accessing the next polygon in the initial
linked list.This is no different from what we did in the first
tutorial. case CP_FRONT:
polyTest->Next=FrontList;
FrontList=polyTest;
break;
case CP_BACK:
polyTest->Next=BackList;
BackList=polyTest;
break;
Nothing to really talk about there then. The last 'case' we need to
consider is if the current Polygon is Spanning the Current Nodes Split
Plane.In this case we have to split this polygon in two and attach the
Front half to the Front List and the back half to the back list. Once
again, if you have read the previous tutorial then all this will be
instantly familiar to you as the code is very similiar. Below shows the
code for the last 'case' as well as the tail end of the main 'while'
loop.case CP_SPANNING:
FrontSplit=new POLYGON;
BackSplit=new POLYGON;
ZeroMemory(FrontSplit,sizeof(POLYGON));
ZeroMemory(BackSplit,sizeof(POLYGON));
SplitPolygon(polyTest,&PlaneArray[NodeArray[Node].Plane],FrontSplit,BackSplit);
FrontSplit->BeenUsedAsSplitter=polyTest->BeenUsedAsSplitter;
BackSplit->BeenUsedAsSplitter=polyTest->BeenUsedAsSplitter;
FrontSplit->TextureIndex=polyTest->TextureIndex;
BackSplit->TextureIndex=polyTest->TextureIndex;
DeletePolygon (polyTest);
FrontSplit->Next=FrontList;
FrontList=FrontSplit;
BackSplit->Next=BackList;
BackList=BackSplit;
break;
default:
break;
} //switch
polyTest=NextPolygon;
}// end while loop
I have highlighted bits that are new from the last tutorial.The
SplitPolygon function is virtually the same so although I will show you
the code later I will not talk about it in depth as I did this in
painstaking detail in the previous tutorial.So whats happening? We
create two new polygons FrontSplit and BackSplit.We then call
SplitPolygon to split the polygon by the current Nodes Split plane.The
two splits will be returned in FrontSplit and BackSplit. The next bit is
VERY IMPORTANT. Before we delete the parent polygon (because we no
longer need it) we copy over the state of its 'BeenUsedAsSplitter'
variable to its children.This means that if the polygon that has just
been split has itself been used as a split plane in the past, then we
dont want to divide the world by the same Split plane twice.So we copy
it over to the children so they can not be selected as splitter again
also.Remember that if this polygon has NOT been used as a split plane
then that state is carried over also, meaning that the Child polygons
can be used as split planes in the future. We also have to copy over
into the FrontSplit and BackSplit polygons what texture the parent used
because obviously they will use the same texture also. Remember from the
previous tutorial that the split polygon function takes care of
recalculating each splits new texture coordinates and also takes care of
retriangulating (is that a word ? :) ) the polygon to still make sure
it remains as a triangle list.CalculateBox(&NodeArray[Node].BoundingBox,FrontList);
BOUNDINGBOX LeafBox=NodeArray[Node].BoundingBox;
CalculateBox(&NodeArray[Node].BoundingBox,BackList);int count=0;
POLYGON * tempf=FrontList;
while (tempf!=NULL)
{
if (tempf->BeenUsedAsSplitter==false) count++;
tempf=tempf->Next;
}
So now it is time to deal with the front and back lists and see if we
have to create more Nodes and carry on splitting etc.First we check if
'count==0' in which case the current Front List IS a leaf. Remember when
looking at the following code that both 'NumberOfPolygons' &
'NumberOfLeafs' are global variables that keep track thoughout the
building of the BSP tree how many polygons and leafs have been added to
the Polygon and Leaf array respectively. Remember also that at start up
we allocated enough space for 100 elements in each array.Also you can
see that the on the first recurse of this code both NumberOfPolygons and
NumberOfLeafs would be zero. if ( count==0)
{
POLYGON *Iterator=FrontList;
POLYGON *Temp;
LeafArray[NumberOfLeafs].StartPolygon=NumberOfPolygons;
while (Iterator!=NULL)
{
PolygonArray[NumberOfPolygons]=*Iterator;
IncreaseNumberOfPolygons();
Temp=Iterator;
Iterator=Iterator->Next;
delete Temp;
}
LeafArray[NumberOfLeafs].EndPolygon=NumberOfPolygons;
LeafArray[NumberOfLeafs].BoundingBox=LeafBox;
NodeArray[Node].Front=NumberOfLeafs;
NodeArray[Node].IsLeaf=1;
IncreaseNumberOfLeafs();
}
OK its time to fill out a new Leaf Element in the Leaf Array.Obviously
this will be Leaf[0] for the first leaf created.We set its StartPolygon
value to the current number of polygons added to the Polygon Array.This
once again will be zero if this is the first leaf because polygons are
only added to the polygon array once they end up in leafs.Infact this is
the code that adds them. We then loop through every polygon in the
front list and copy it into the Polygon Array using
'PolygonArray[NumberOfPolygons]=*Iterator'. Next up we call the function
'IncreaseNumberOfPolygons' which needs explaining. This function
basically just adds 1 on to the NumberOfPolygons variable BUT if
'NumberOfPolygons' gets larger than MAXNUMBEROFPOLYGONS (which initially
starts off at 100 remember) then the Array is resized using realloc.
Each of the global arrays (PolygonArray,NodeArray,PlaneArray etc) has a
corresponding function like this.else
{
NodeArray[Node].IsLeaf=0;
NodeArray[Node].Front=NumberOfNodes+1;
IncreaseNumberOfNodes();
BuildBspTree(NumberOfNodes,FrontList);
}
You can see that we set 'IsLeaf' to zero because the Current Nodes
'Front' field will not contain the index of a leaf but instead an index
of another node in the Node Array.So we set this Nodes 'Front' to index
the 'Next Node we are About to Create(NumberOfNodes+1)' and then
increase the NumberOfNodes variable which is now holding the index of
this new node.Therefore the function calls itself passing in the New
Node (CurrentNode+1) index in the array and passing in the CurrentFront
list as the New Nodes PolygonList. Thats the Front List dealt with.
if (BackList==NULL)
{
NodeArray[Node].Back=-1;
}
else
{
NodeArray[Node].Back=NumberOfNodes+1;
IncreaseNumberOfNodes();
BuildBspTree(NumberOfNodes,BackList);
}
}// end function
BuildBSPTree function complete. I will now show the function in its
entirety so you can view it in its complete form and after that we will
talk about the 'support' functions that BuildBSPTree uses.void BuildBspTree(long Node,POLYGON * PolyList)
{
POLYGON *polyTest=NULL;
POLYGON *FrontList=NULL;
POLYGON *BackList=NULL;
POLYGON *NextPolygon=NULL;
POLYGON *FrontSplit=NULL;
POLYGON *BackSplit=NULL;
D3DXVECTOR3 vec1,vec2;
D3DXVECTOR3 a,b;
float result;
NodeArray[Node].Plane=SelectBestSplitter(PolyList);
polyTest=PolyList;
NodeArray[Node].BoundingBox.BoxMax=D3DXVECTOR3(-40000,-40000,-40000);
NodeArray[Node].BoundingBox.BoxMin=D3DXVECTOR3(40000,40000,40000);
while (polyTest!=NULL )
{
NextPolygon=polyTest->Next;// remember because polytest->Next will be altered
switch (ClassifyPoly(&PlaneArray[NodeArray[Node].Plane],polyTest))
{
case CP_ONPLANE:
a=PlaneArray[NodeArray[Node].Plane].Normal;
b=polyTest->Normal;
result=(float)fabs ((a.x-b.x)+(a.y-b.y)+(a.z-b.z));
if (result<0.1)
{
polyTest->Next=FrontList;
FrontList=polyTest;
}
else
{
polyTest->Next=BackList;
BackList=polyTest;
}
break;
case CP_FRONT:
polyTest->Next=FrontList;
FrontList=polyTest;
break;
case CP_BACK:
polyTest->Next=BackList;
BackList=polyTest;
break;
case CP_SPANNING:
FrontSplit=new POLYGON;
BackSplit=new POLYGON;
ZeroMemory(FrontSplit,sizeof(POLYGON));
ZeroMemory(BackSplit,sizeof(POLYGON));
SplitPolygon(polyTest,&PlaneArray[NodeArray[Node].Plane],FrontSplit,BackSplit);
FrontSplit->BeenUsedAsSplitter=polyTest->BeenUsedAsSplitter;
BackSplit->BeenUsedAsSplitter=polyTest->BeenUsedAsSplitter;
FrontSplit->TextureIndex=polyTest->TextureIndex;
BackSplit->TextureIndex=polyTest->TextureIndex;
DeletePolygon (polyTest);
FrontSplit->Next=FrontList;
FrontList=FrontSplit;
BackSplit->Next=BackList;
BackList=BackSplit;
break;
default:
break;
} //switch
polyTest=NextPolygon;
}// end while loop
int count=0;
POLYGON * tempf=FrontList;
while (tempf!=NULL)
{
if (tempf->BeenUsedAsSplitter==false) count++;
tempf=tempf->Next;
}
CalculateBox(&NodeArray[Node].BoundingBox,FrontList);
BOUNDINGBOX LeafBox=NodeArray[Node].BoundingBox;
CalculateBox(&NodeArray[Node].BoundingBox,BackList);
if ( count==0)
{
POLYGON *Iterator=FrontList;
POLYGON *Temp;
LeafArray[NumberOfLeafs].StartPolygon=NumberOfPolygons;
while (Iterator!=NULL)
{
PolygonArray[NumberOfPolygons]=*Iterator;
IncreaseNumberOfPolygons();
Temp=Iterator;
Iterator=Iterator->Next;
delete Temp;
}
LeafArray[NumberOfLeafs].EndPolygon=NumberOfPolygons;
LeafArray[NumberOfLeafs].BoundingBox=LeafBox;
NodeArray[Node].Front=NumberOfLeafs;
NodeArray[Node].IsLeaf=1;
IncreaseNumberOfLeafs();
}
else
{
NodeArray[Node].IsLeaf=0;
NodeArray[Node].Front=NumberOfNodes+1;
IncreaseNumberOfNodes();
BuildBspTree(NumberOfNodes,FrontList);
}
if (BackList==NULL)
{
NodeArray[Node].Back=-1;
}
else
{
NodeArray[Node].Back=NumberOfNodes+1;
IncreaseNumberOfNodes();
BuildBspTree(NumberOfNodes,BackList);
}
}// end function
You can see above that I have highlighted in Red where support functions
have been used.Read the above code and make sure you understand how the
tree is being built.It does not matter whether or not you know how the
support functions work we will look at these in a moment but make sure
you can see above what they are being used for and what they are
returning to the calling function.
NodeArray[Node].Plane=SelectBestSplitter(PolyList); long SelectBestSplitter(POLYGON *PolyList)
{
POLYGON* Splitter=PolyList;
POLYGON* CurrentPoly=NULL;
unsigned long BestScore=1000000;
POLYGON * SelectedPoly=NULL;
while (Splitter!=NULL)
{
if (Splitter->BeenUsedAsSplitter!=true)
{
PLANE SplittersPlane;
SplittersPlane.Normal=Splitter->Normal;
SplittersPlane.PointOnPlane=*(D3DXVECTOR3 *)&Splitter->VertexList[0];
CurrentPoly=PolyList;
unsigned long score,splits,backfaces,frontfaces;
score=splits=backfaces=frontfaces=0;
while (CurrentPoly!=NULL)
{
int result=ClassifyPoly(&SplittersPlane,CurrentPoly);
switch ( result)
{
case CP_ONPLANE:
break;
case CP_FRONT:
frontfaces++;
break;
case CP_BACK:
backfaces++;
break;
case CP_SPANNING:
splits++;
break;
default:
break;
}// switch
CurrentPoly=CurrentPoly->Next;
}// end while current poly
score=abs(frontfaces-backfaces)+(splits*3);
if (score<BestScore)
{
BestScore=score;
SelectedPoly=Splitter;
}
}// end if this splitter has not been used yet
Splitter=Splitter->Next;
}// end while splitter != null
SelectedPoly->BeenUsedAsSplitter=true;
PlaneArray[NumberOfPlanes].PointOnPlane=*((D3DXVECTOR3 *)&SelectedPoly->VertexList[0]);
PlaneArray[NumberOfPlanes].Normal=SelectedPoly->Normal;
IncreaseNumberOfPlanes();
return (NumberOfPlanes-1);
} // End Function
You should recognize most of this stuff from the previous tutorial but there are some differences so I will summerize. void CalculateBox(BOUNDINGBOX *Box,POLYGON *Polylist)
{
WORD i;
POLYGON *PolyPointer=Polylist ;
while (PolyPointer!=NULL)
{
for (i=0;i<PolyPointer->NumberOfVertices;i++)
{
// check Minimum Bounds
if (PolyPointer->VertexList[i].x<Box->BoxMin.x) Box->BoxMin.x=PolyPointer->VertexList[i].x;
if (PolyPointer->VertexList[i].y<Box->BoxMin.y) Box->BoxMin.y=PolyPointer->VertexList[i].y;
if (PolyPointer->VertexList[i].z<Box->BoxMin.z) Box->BoxMin.z=PolyPointer->VertexList[i].z;
// check Maximum Bounds
if (PolyPointer->VertexList[i].x>Box->BoxMax.x) Box->BoxMax.x=PolyPointer->VertexList[i].x;
if (PolyPointer->VertexList[i].y>Box->BoxMax.y) Box->BoxMax.y=PolyPointer->VertexList[i].y;
if (PolyPointer->VertexList[i].z>Box->BoxMax.z) Box->BoxMax.z=PolyPointer->VertexList[i].z;
}
PolyPointer=PolyPointer->Next;
}
}
Another support function used by the BuildBSPTree function is one
called 'DeletePolygon' and this function can be found in
'memoryalloc.cpp'.We call this function whenever we want to remove a
polygon from memory.Remember that calling the c++ 'delete' command will
remove the memory allocated by the Polygon structure itself BUT will not
remove the memory allocated dynamically for the Vertex and Index arrays
that are pointed to by the polygon structure.This would cause a memory
leak.In short then we have to delete the Index and Vertex List memory
first (while we still have pointers to the memory) and then delete the
Polygon structure. Here is the DeletePolygon function.void DeletePolygon (POLYGON *Poly)
{
delete Poly->VertexList;
delete Poly->Indices;
delete Poly;
}
Another thing that can look quite confusing is the fact that when we
copy over the FrontList polygons over into a new Leaf (if count==0) we
do infact call the c++ command 'delete' instead of our function
'DeletePolygons'.This is because although we no longer need the polygon
structure (the one in the front list) itself any more the memory for
the Vertex Array and the Index Array will still be pointed at by the
polygon structure in the PolygonArray.There for we do not want the
vertex or index memory released.
void IncreaseNumberOfNodes(void)
{
NumberOfNodes++;
if (NumberOfNodes==MAXNUMBEROFNODES)
{
MAXNUMBEROFNODES+=100;
NodeArray=(NODE *)realloc (NodeArray,MAXNUMBEROFNODES * sizeof(NODE));
}
}
You can see that we increase NumberOfNodes variable and also check if
this takes the number of nodes past the current size of the array.If so
we add 100 to the size of the array and resize the memory block calling
the C function 'realloc'.
int ClassifyPoly(PLANE *Plane,POLYGON * Poly)
{
int Infront=0;
int Behind=0;
int OnPlane=0;
float result;
D3DXVECTOR3 *vec1=(D3DXVECTOR3 *)&Plane->PointOnPlane;
for (int a=0;a<Poly->NumberOfVertices;a++)
{
D3DXVECTOR3 *vec2=(D3DXVECTOR3 *)&Poly->VertexList[a];
D3DXVECTOR3 Direction=(*vec1)-(*vec2);
result=D3DXVec3Dot(Direction,Plane->Normal);
if (result>0.001)
{
Behind++;
}
else
if (result<-0.001)
{
Infront++;
}
else
{
OnPlane++;
Infront++;
Behind++;
}
}
if (OnPlane==Poly->NumberOfVertices) return CP_ONPLANE;
if (Behind==Poly->NumberOfVertices) return CP_BACK;
if (Infront==Poly->NumberOfVertices) return CP_FRONT;
return CP_SPANNING;
}
The last function in the BuildBSPTree function that we have not talked
about is the SplitPolygon function.As I said earlier there really was a
quite large and detailed explanation about this function in the Part 1
tutorial so please refer back to this tutorial if you are having trouble
remembering or if you do not understand the code. For those of you that
can remember I will just show it here as a refresher and to show whats
changed (as of writing I have not yet updated Part1 from dx7 to dx8 so
here it is in its new form using the D3DX functions instead of the D3D
Framework from dx7. Apart from these changes it is exactly the same).void SplitPolygon(POLYGON *Poly,PLANE *Plane,POLYGON *FrontSplit,POLYGON *BackSplit)
{
D3DLVERTEX FrontList[40],BackList[40],FirstVertex;
D3DXVECTOR3 PlaneNormal,IntersectPoint,PointOnPlane,PointA,PointB;
WORD FrontCounter=0,BackCounter=0,loop=0,CurrentVertex=0;
float percent; // used for return storage of percentage
// Find any vertex on the plane to use later in plane intersect routine
PointOnPlane=Plane->PointOnPlane;
// first we find out if the first vertex belongs in front or back list
FirstVertex=Poly->VertexList[0];
switch (ClassifyPoint( (D3DXVECTOR3 *)&FirstVertex,Plane))
{
case CP_FRONT:
FrontList[FrontCounter++]=FirstVertex;
break;
case CP_BACK:
BackList[BackCounter++]=FirstVertex;
break;
case CP_ONPLANE:
BackList[BackCounter++]=FirstVertex;
FrontList[FrontCounter++]=FirstVertex;
break;
default:
PostQuitMessage(0);;
}
for (loop=1;loop<Poly->NumberOfVertices+1;loop++)
{
if (loop==Poly->NumberOfVertices)
{
CurrentVertex=0;
}
else
{
CurrentVertex=loop;
}
PointA=*(D3DXVECTOR3 *)&Poly->VertexList[loop-1];
PointB=*(D3DXVECTOR3 *)&Poly->VertexList[CurrentVertex];
PlaneNormal=Plane->Normal;
int Result=ClassifyPoint(&PointB,Plane);
if (Result==CP_ONPLANE)
{
BackList[BackCounter++]=Poly->VertexList[CurrentVertex];
FrontList[FrontCounter++]=Poly->VertexList[CurrentVertex];
}
else
{
if (Get_Intersect(&PointA,&PointB,&PointOnPlane,&PlaneNormal,&IntersectPoint, &percent)==true)
{
// create new vertex and calculate new texture coordinate
float deltax,deltay,texx,texy;
deltax=Poly->VertexList[CurrentVertex].tu-Poly->VertexList[loop-1].tu;
deltay=Poly->VertexList[CurrentVertex].tv-Poly->VertexList[loop-1].tv;
texx=Poly->VertexList[loop-1].tu+(deltax*percent);
texy=Poly->VertexList[loop-1].tv+(deltay*percent);
D3DLVERTEX copy;//=D3DLVERTEX(IntersectPoint,D3DCOLOR_ARGB(255,255,255,255),0,texx,texy);
copy.x=IntersectPoint.x;
copy.y=IntersectPoint.y;
copy.z=IntersectPoint.z;
copy.color=D3DCOLOR_ARGB(255,255,255,255);
copy.specular=0;
copy.tu=texx;
copy.tv=texy;
if (Result==CP_FRONT )
{
BackList[BackCounter++]=copy;
FrontList[FrontCounter++]=copy;
if (CurrentVertex!=0)
{
FrontList[FrontCounter++]=Poly->VertexList[CurrentVertex];
}
}
if (Result==CP_BACK)
{
FrontList[FrontCounter++]=copy;
BackList[BackCounter++]=copy;
if (CurrentVertex!=0)
{
BackList[BackCounter++]=Poly->VertexList[CurrentVertex];
}
}
}// end get intersect
else
{
if (Result==CP_FRONT)
{
if (CurrentVertex!=0)
{
FrontList[FrontCounter++]=Poly->VertexList[CurrentVertex];
}
}
if (Result==CP_BACK)
{
if (CurrentVertex!=0)
{
BackList[BackCounter++]=Poly->VertexList[CurrentVertex];
}
}
}
}// end for not onplane
}//end for loop
//OK THEN LETS BUILD THESE TWO POLYGONAL BAD BOYS :)
FrontSplit->NumberOfVertices=0;
BackSplit->NumberOfVertices=0;
// Reserve Memory for Front and Back Vertex Lists
FrontSplit ->VertexList= new D3DLVERTEX [FrontCounter];
BackSplit ->VertexList= new D3DLVERTEX [BackCounter ];
for (loop=0;loop<FrontCounter;loop++)
{
FrontSplit->NumberOfVertices++;
FrontSplit->VertexList[loop]=FrontList[loop];
}
for (loop=0;loop<BackCounter;loop++)
{
BackSplit->NumberOfVertices++;
BackSplit->VertexList[loop]=BackList[loop];
}
BackSplit->NumberOfIndices=(BackSplit->NumberOfVertices-2)*3;
FrontSplit->NumberOfIndices=(FrontSplit->NumberOfVertices-2)*3;
// Reserve Memory for Front and Back Index Lists
FrontSplit ->Indices= new WORD [FrontSplit->NumberOfIndices];
BackSplit ->Indices= new WORD [BackSplit ->NumberOfIndices];
// Fill in the Indices
WORD v0,v1,v2;
for (loop=0;loop<FrontSplit->NumberOfIndices/3;loop++)
{
if (loop==0)
{
v0=0;
v1=1;
v2=2;
}
else
{
v1=v2;
v2++;
}
FrontSplit->Indices[loop*3]=v0;
FrontSplit->Indices[(loop*3)+1]=v1;
FrontSplit->Indices[(loop*3)+2]=v2;
}
for (loop=0;loop<BackSplit->NumberOfIndices/3;loop++)
{
if (loop==0)
{
v0=0;
v1=1;
v2=2;
}
else
{
v1=v2;
v2++;
}
BackSplit->Indices[loop*3]=v0;
BackSplit->Indices[(loop*3)+1]=v1;
BackSplit->Indices[(loop*3)+2]=v2;
}
FrontSplit->Normal=Poly->Normal;
BackSplit->Normal=Poly->Normal;
}
void RenderTree(D3DVECTOR pos)
{
int Node=0;
int leaf=0;
This function will be called once per frame to render the tree and will
be passed in the Position of the Camera/Player in the game world.. This
code finds the LEAF the camera is in and then calls DrawTree to render
that leaf.We set 'Node' to zero at first obviously because we need to
start testing our camera position at the root of the tree.while(1)
{
switch(ClassifyPoint(&pos,&PlaneArray[NodeArray[Node].Plane]))
{
case CP_FRONT:
if (NodeArray[Node].IsLeaf!=0)
{
leaf=NodeArray[Node].Front;
DrawTree(leaf);
return;
}
else
{
Node = NodeArray[Node].Front;
}
break;
If the camera position is infront of this 'Node' then we need to travel
down this Nodes front tree to further refine the search.HOWEVER, the
Front of this tree may not point to another Node and may in fact be the
Leaf we are looking for so we check the Nodes 'IsLeaf' variable to see
of this is the case.If 'IsLeaf!=0'
then we are at the end branch of this tree and this Nodes Front pointer
actually contains the Index(into the LeafArray) of the Leaf we are
in.This means our search is over.We simply call 'DrawTree' and pass in
this Index so that the DrawTree function can render the Leafs PVS Set.
Then we return from this function because the job is done.case CP_BACK:
if (NodeArray[Node].Back==-1)
{
return;
}
else
{
Node = NodeArray[Node].Back;
}
break;
As with the 'CP_FRONT' case we have to check to see that we can go down
the Back of this node.If this Nodes 'Back' variable equals '-1' then
there are no more Nodes to travserse and the camera is infact in solid
space. This should NEVER happen becuse it would mean that your camera is
inside a wall or something like that but we dont want any crashes
before you implement your collision detection because then you could
walk through a wall accidentally.To keep things safe I simply return if
the camera is in solid space which means nothing gets rendered.Like I
said though, this should never happen.case CP_ONPLANE:
if (NodeArray[Node].IsLeaf!=0)
{
leaf=NodeArray[Node].Front;
DrawTree(leaf);
return;
}
else
{
Node = NodeArray[Node].Front;
}
default:
break;
}
}// end while
}// End Function void RenderTree(D3DVECTOR pos)
{
int Node=0;
int leaf=0;
while(1)
{
switch(ClassifyPoint(&pos,&PlaneArray[NodeArray[Node].Plane]))
{
case CP_FRONT:
if (NodeArray[Node].IsLeaf!=0)
{
leaf=NodeArray[Node].Front;
DrawTree(leaf);
return;
}
else
{
Node = NodeArray[Node].Front;
}
break;
case CP_BACK:
if (NodeArray[Node].Back==-1)
{
return;
}
else
{
Node = NodeArray[Node].Back;
}
break;
case CP_ONPLANE:
if (NodeArray[Node].IsLeaf!=0)
{
leaf=NodeArray[Node].Front;
DrawTree(leaf);
return;
}
else
{
Node = NodeArray[Node].Front;
}
default:
break;
}
}// end while
}// End Functionvoid DrawTree(long leaf)
{
POLYGON *CurrentPoly;
int i;
long PVSOFFSET=LeafArray[leaf].PVSIndex;
BYTE *PVSPointer=PVSData;
PVSPointer+=PVSOFFSET;
long currentleaf=0;
Above you can see the code creates a BYTE pointer to point to the first
BYTE of this Leafs Visibility information.The Leaf itself holds the
Number of Bytes in from the beginning of the array (stored in PVSIndex)
so this is the offset into the master PVSData array where this leafs
visibility information starts.So we create a pointer to the PVSData
array and simply add the offset on.PVSPointer now points at this Leafs
PVS information. Current Leaf is set to zero. This variable will keep
track of which leaf in THIS leafs PVS information we are currently
checking the visibility BIT of. When Current Leaf equals NumberOfLeafs
we know we have checked all the leafs in this leafs PVS information and
nothing more has to be rendered.// Set All the Texture Batch Pointers to NULL
for (i=0;i<25;i++)
{
pTexturePolygonList[i]=NULL;
}
Ok thats done. Now its time to check the Visibility information for this
leaf.We create a while loop that wil exit once
CurrentLeaf=NumberOfLeafs because this means we have checked all the
visibility information and we now have our 25 linked lists of polygons
ready to be rendered..
while (currentleaf<NumberOfLeafs)
{
if (*PVSPointer!=0)
{
for (i=0;i<8;i++)
{
BYTE mask=1<<i;
BYTE pvs=*PVSPointer;
if (pvs&mask)
{
First we check if the current Byte is non zero.If it is non zero then it
means some of the BITs are set to one in this byte so at least one(but
could be all) of the leafs represented by this byte must be visible.
if (LeafInFrustum(currentleaf)==true || DontFrustumReject==true)
{ unsigned long start=LeafArray[currentleaf].StartPolygon;
unsigned long finish=LeafArray[currentleaf].EndPolygon;
unsigned long Count=0;
for (Count=start;Count<finish;Count++) // Collect the polys and add to Texture List
{
CurrentPoly=&PolygonArray[Count];
CurrentPoly->Next=pTexturePolygonList[CurrentPoly->TextureIndex];
pTexturePolygonList[CurrentPoly->TextureIndex]=CurrentPoly;
}
}// end if leaf infrustum
}// end for if pvsdata
currentleaf++;
}// end for i;
PVSPointer++;
}
else
{// we have hit a zero so read in next byte for run length
PVSPointer++;
BYTE RunLength=*PVSPointer;
PVSPointer++;
currentleaf+=RunLength*8;
}
}// end for while
Above shows what happen if the Current Byte the PVS data is zero. We
read in the next Byte which will tell us how many zero bytes are in a
run and we adjust our 'currentleaf' parameter so that it skips past all
the leafs in the run. Remember that RunLength is the number of bytes in
th run and each byte hold 8 bits of information for each leaf so that is
why we adjust 'currentleaf' by 'Runlength*8'.
for (i=0;i<NumberOfTextures;i++)
{
if (pTexturePolygonList[i]!=NULL)
{
lpDevice->SetTexture(0,lpTextureSurface[i]);
CurrentPoly=pTexturePolygonList[i];
lpDevice->SetVertexShader( D3DFVF_LVERTEX );
while (CurrentPoly!=NULL)
{
lpDevice->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST,0,CurrentPoly->NumberOfVertices,
CurrentPoly->NumberOfIndices/3,&CurrentPoly->Indices[0],
D3DFMT_INDEX16 ,&CurrentPoly->VertexList[0],sizeof(D3DLVERTEX));
CurrentPoly=CurrentPoly->Next;
}
}
}
lpDevice->SetTexture(0,NULL);
}//End Function
All done . Scene rendered. Above we Set the current texture in
TextureStage 0 and also use the Temporary variable 'CurrentPoly' to
point at the head of each List.This will be used to step though each
polygon list.We then use 'CurrentPoly' to iterate through the Linked
List until we reach the end of that list.When there is a polygon we
Render it using DrawIndexedPrimitiveUP.|
DX8 NOTE For those of you that have not used DrawIndexedPrimitiveUP before the syntax is:- DrawIndexedPrimitiveUP(
'MinIndex' is the index of the Minimum Vertex in the vertex array that
will be used by this call.Because every Polygon in the scene has its own
Vertex List we want to use all the vertices for this polygon so we
start at zero. Primitive Count is a new parameter also.Instead of
specifying the Number OF indices you now specify how many primitives
will have to be rendered to make up this polygon.Remember your polygon
may have been broken down into many triangles by the 'AddPolygon'
function. For triangle Lists it works out we can just divide the Number
Of Indices by three to get the primitive count. Above I have used
D3DFMT_INDEX16 to tell direct3d that my Indices array holds 16bit (WORD)
values.You can have 32 bit indices now if you need them. The one from
last parameter is just a pointer to your vertex data and the last
parameter describes the size to step between each vertex to get to the
next one in the array (in bytes).This is equal tothe size of out
D3DLVERTX structure.
|
void DrawTree(long leaf)
{
POLYGON *CurrentPoly;
int i;
long PVSOFFSET=LeafArray[leaf].PVSIndex;
BYTE *PVSPointer=PVSData;
PVSPointer+=PVSOFFSET;
long currentleaf=0;
// Set All the Texture Batch Pointers to NULL
for (i=0;i<25;i++)
{
pTexturePolygonList[i]=NULL;
}
while (currentleaf<NumberOfLeafs)
{
if (*PVSPointer!=0)
{
for (i=0;i<8;i++)
{
BYTE mask=1<<i;
BYTE pvs=*PVSPointer;
if (pvs&mask)
{
if (LeafInFrustum(currentleaf)==true || DontFrustumReject==true)
{
unsigned long start=LeafArray[currentleaf].StartPolygon;
unsigned long finish=LeafArray[currentleaf].EndPolygon;
unsigned long Count=0;
for (Count=start;Count<finish;Count++)
{
CurrentPoly=&PolygonArray[Count];
CurrentPoly->Next=pTexturePolygonList[CurrentPoly->TextureIndex];
pTexturePolygonList[CurrentPoly->TextureIndex]=CurrentPoly;
}
}// end if leaf infrustum
}// end for if pvsdata
currentleaf++;
}// end for i;
PVSPointer++;
}// if pvspointer!=0; In other words if this is not a compressed byte
else
{// we have hit a zero so read in the next byte and see how long the run of zeros is
PVSPointer++;
BYTE RunLength=*PVSPointer;
PVSPointer++;
currentleaf+=RunLength*8;
}
}// end for while
//Render Polygons in the textures lists.
for (i=0;i<NumberOfTextures;i++)
{
if (pTexturePolygonList[i]!=NULL)
{
lpDevice->SetTexture(0,lpTextureSurface[i]);
CurrentPoly=pTexturePolygonList[i];
lpDevice->SetVertexShader( D3DFVF_LVERTEX );
while (CurrentPoly!=NULL)
{
lpDevice->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST,0,CurrentPoly->NumberOfVertices,
CurrentPoly->NumberOfIndices/3,&CurrentPoly->Indices[0],
D3DFMT_INDEX16 ,&CurrentPoly->VertexList[0],sizeof(D3DLVERTEX));
CurrentPoly=CurrentPoly->Next;
}
}
}
lpDevice->SetTexture(0,NULL);
}//End Function
bool LineOfSight (D3DXVECTOR3 *Start,D3DXVECTOR3 *End, long Node)
{
float temp;
D3DXVECTOR3 intersection;
NODE *CurrNode=&NodeArray[Node];
PLANE *Plane=&PlaneArray[CurrNode->Plane];
int PointA=ClassifyPoint(Start, Plane);
int PointB=ClassifyPoint(End, Plane);
Point A and Point B hold the Start and End points of the Ray we are
using to test against the tree.If BOTH points are on the Current Nodes
Planes then we will simply treat it as being in front of the plane and
recur down the Front of this node.We check this Nodes IsLeaf variable
just to check that there is another Node down the front tree of this
node.If there is then we recurse down that tree with the Ray.If IsLeaf
is non zero however then there is not another NOde and the Front
Variable holds the Leaf index.We can return true because Leafs are empty
space and both points are obviously in this leaf.
if (PointA==CP_ONPLANE && PointB==CP_ONPLANE)
{
if (CurrNode->IsLeaf==0)// this is not a leaf so recurse
{
return LineOfSight(Start,End,CurrNode->Front);
}
else
{
return true; // This is a front leaf.Front Leafs are always empty so this is empty space
}
}
if (PointA==CP_FRONT && PointB==CP_BACK)
{
if (CurrNode->Back==-1) return false;
Get_Intersect (Start,End,&Plane->PointOnPlane,&Plane->Normal,&intersection,&temp);
if (CurrNode->IsLeaf==0)
{
return LineOfSight(Start,&intersection,CurrNode->Front)
&& LineOfSight(End,&intersection,CurrNode->Back) ;
}
else
{
return true && LineOfSight(End,&intersection,CurrNode->Back);
}
}
if (PointA==CP_BACK && PointB==CP_FRONT)
{
if (CurrNode->Back==-1) return false;
Get_Intersect (Start,End,&Plane->PointOnPlane,&Plane->Normal,&intersection,&temp);
if (CurrNode->IsLeaf==0)
{
return LineOfSight(End,&intersection,CurrNode->Front)
&& LineOfSight(Start,&intersection,CurrNode->Back) ;
}
else
{
return true && LineOfSight(Start,&intersection,CurrNode->Back);
}
}
If we get as far as here it must mean one of the points is on the plane
and the other is not.In this case we treat the point ON the plane as
being the same as the Point not on the plane.In other words we send it
down the side of the tree that the other point lays in.
if (PointA==CP_FRONT || PointB==CP_FRONT)
{
if (CurrNode->IsLeaf==0)
{
return LineOfSight(Start,End,CurrNode->Front);
}
else
{
return true;
}
}
else
{
if (CurrNode->Back==-1)
{
return false;
}
else
{
return LineOfSight(Start,End,CurrNode->Back);
}
}
return true;
}
Thankfully we have now Ported over all our Solid Node Based
functionality to work with the Solid Leaf Tree which means its time to
move on to the interesting stuff. Please make sure you understand
everything we have talked about above before reading on because we are
now going to cover calculating the PVS for the tree and it is vital that
you understand how the Solid Leaf Tree works and how it is laid out in
memory. With that said, lets move onto the good stuff.

struct LEAF{
long StartPolygon;
long EndPolygon;
long PortalIndexList[50];
long NumberOfPortals;
long PVSIndex;
BOUNDINGBOX BoundingBox;
};
Above I have highlighted the two fields we have not yet
discussed.NumberOfPortals simply holds the NumberOfPortals that this
leaf has in it.Remember that a room could have many many doorways.
PortalIndexList is just an array in indexes into the master Portal
Array.We will talk about the Portal Stucture in a moment but just know
for now that this array is just like the other master arrays
(LeafArray,NodeArray etc) except that it holds Pointers to PORTALs.When
the Valid Portals are created they will be added to this array.





struct PORTAL
{
D3DLVERTEX *VertexList;
D3DXVECTOR3 Normal;
WORD NumberOfVertices;
WORD NumberOfIndices;
WORD *Indices;
PORTAL * Next;
PORTAL * Prev;
BYTE NumberOfLeafs;
long LeafOwnerArray[2];
};
This first five fields of this structure are the same as the POLYGON
structure.This allows us to easily cast a PORTAL pointer to a POLYGON
Pointer so that we can pass in Portals to our
'SplitPolygon','ClassifyPoly' & 'ClassifyPoint' functions.This
structure though has a Next and a Prev pointer that will be used to
point to other portals in linked lists.We use the previous portal so
that we can remove portals from the linked list without destroying the
chain.We will see this in action shortly.PORTAL * CalculateInitialPortal(long Node)
{
D3DXVECTOR3 MaxP,MinP,CB,CP;
MaxP=NodeArray[Node].BoundingBox.BoxMax;// Get this Nodes Bounding Box ranges
MinP=NodeArray[Node].BoundingBox.BoxMin;
D3DXVECTOR3 PlaneNormal=PlaneArray[NodeArray[Node].Plane].Normal;
CB=(MaxP+MinP)/2;
float DistanceToPlane=D3DXVec3Dot(&(PlaneArray[NodeArray[Node].Plane].PointOnPlane-CB),&PlaneNormal);
CP=CB+(PlaneNormal*DistanceToPlane);
First we get the Nodes Bounding Box MAX and MIN points and use them to
calculate the Center of the Bounding Box ( CB ). Next we use the dot
product to find the distance from 'CB' to the Plane. This is just
standard DotProduct stuff so if you are not familiar you may want to
read our DotProduct
Tutorial. Anyway if we create a Vector from the Box Center Point (CP)
to a Point known to be on the Plane (we have this stored in the Nodes
Plane structure) and then Dotproduct that vector with the Plane Normal
we get returned the Shorted Distance to the plane from this point. If we
travel in the direction of the Plane Normal from 'CB' we will be on the
plane at point 'CP'.CP will be the Center of the Portal we are going to
create. Look below at Figure Q which shows a Portal being calculated
(SIDE VIEW like a cross section).
D3DXVECTOR3 A=D3DXVECTOR3(0.0f,0.0f,0.0f);
if( fabs(PlaneNormal.y) > fabs(PlaneNormal.z) )
{
if( fabs(PlaneNormal.z) < fabs(PlaneNormal.x) )
{
A.z = 1;
}
else
{
A.x = 1;
}
}
else
{
if (fabs(PlaneNormal.y )<= fabs(PlaneNormal.x) )
{
A.y = 1;
}
else
{
A.x = 1;
}
}
At this point, Vector 'A' will either contain (1,0,0),(0,1,0) or (0,0,1)
depending on which vector is least aligned with the plane normal. All
we have to do now is Cross Vector A with the Plane Normal to get Vector U
(our right vector).We Normalize (make it a unit vector) U and then
Cross 'U' with the Plane Nomral to get Vector 'V' our up vector.Once
again Normalizing the Result.D3DXVec3Cross (&U,&A,&PlaneNormal);
D3DXVec3Normalize(&U,&U);
D3DXVec3Cross (&V,&U,&PlaneNormal);
D3DXVec3Normalize(&V,&V);
We now have our U and V vectors which along with our Plane Normal vector
describes the Local Axis of the Portal. At this point I am now going to
show Figure Q) again to save you scrolling back up everytime I refer to
the diagram.
D3DXVECTOR3 BoxHalfLength=(MaxP-CB);
float Length=D3DXVec3Length(&BoxHalfLength);
U=U*Length;
V=V*Length;
With these Vectors now scaled like this we can create the the four vertices for our portal like so:-D3DXVECTOR3 P[4];
P[0] = CP + U - V;// Bottom Right
P[1] = CP + U + V;// Top Right
P[2] = CP - U + V;// Top Left
P[3] = CP - U - V;// Bottom Left
Thats the vertices created for the portal.Now we just fill in the Portal
Structure much the same way we fill in the Polygon Structure in the
'AddPolygon' structure.We break the square into two triangles indexed by
6 indices.PORTAL *Portal=new PORTAL;
ZeroMemory(Portal,sizeof(PORTAL));
Portal->Normal=PlaneNormal;
Portal->NumberOfVertices=4;
Portal->NumberOfIndices=6;
Portal->VertexList = new D3DLVERTEX [Portal->NumberOfVertices];
Portal->Indices = new WORD [Portal->NumberOfIndices ];
for (int i=0;i<4;i++)
{
Portal->VertexList[i].x=P[i].x;
Portal->VertexList[i].y=P[i].y;
Portal->VertexList[i].z=P[i].z;
Portal->VertexList[i].color=D3DCOLOR_RGBA(rand()%255,rand()%255,rand()%255,240);
Portal->VertexList[i].specular=0;
Portal->VertexList[i].tu=0;
Portal->VertexList[i].tv=0;
}
Portal->Indices[0]=0;
Portal->Indices[1]=1;
Portal->Indices[2]=3;
Portal->Indices[3]=3;
Portal->Indices[4]=1;
Portal->Indices[5]=2;
Portal->Next=NULL;
Portal->Prev=NULL;
Portal->NumberOfLeafs=0;
return Portal;
}
Thats the 'CalculateInitialPortal' function done then. I will just show
it in its complete form now (without my interruptions every couple of
lines) so you can follow it more easily.PORTAL * CalculateInitialPortal(long Node)
{
D3DXVECTOR3 MaxP,MinP,CB,CP;
MaxP=NodeArray[Node].BoundingBox.BoxMax;// Get this Nodes Bounding Box ranges
MinP=NodeArray[Node].BoundingBox.BoxMin;
D3DXVECTOR3 PlaneNormal=PlaneArray[NodeArray[Node].Plane].Normal;
CB=(MaxP+MinP)/2;
float DistanceToPlane=D3DXVec3Dot(&(PlaneArray[NodeArray[Node].Plane].PointOnPlane-CB),&PlaneNormal);
CP=CB+(PlaneNormal*DistanceToPlane);
D3DXVECTOR3 A=D3DXVECTOR3(0.0f,0.0f,0.0f);
if( fabs(PlaneNormal.y) > fabs(PlaneNormal.z) )
{
if( fabs(PlaneNormal.z) < fabs(PlaneNormal.x) )
{
A.z = 1;
}
else
{
A.x = 1;
}
}
else
{
if (fabs(PlaneNormal.y )<= fabs(PlaneNormal.x) )
{
A.y = 1;
}
else
{
A.x = 1;
}
}
D3DXVec3Cross (&U,&A,&PlaneNormal);
D3DXVec3Normalize(&U,&U);
D3DXVec3Cross (&V,&U,&PlaneNormal);
D3DXVec3Normalize(&V,&V);
D3DXVECTOR3 BoxHalfLength=(MaxP-CB);
float Length=D3DXVec3Length(&BoxHalfLength);
U=U*Length;
V=V*Length;
D3DXVECTOR3 P[4];
P[0] = CP + U - V;// Bottom Right
P[1] = CP + U + V;// Top Right
P[2] = CP - U + V;// Top Left
P[3] = CP - U - V;// Bottom Left
PORTAL *Portal=new PORTAL;
ZeroMemory(Portal,sizeof(PORTAL));
Portal->Normal=PlaneNormal;
Portal->NumberOfVertices=4;
Portal->NumberOfIndices=6;
Portal->VertexList = new D3DLVERTEX [Portal->NumberOfVertices];
Portal->Indices = new WORD [Portal->NumberOfIndices ];
for (int i=0;i<4;i++)
{
Portal->VertexList[i].x=P[i].x;
Portal->VertexList[i].y=P[i].y;
Portal->VertexList[i].z=P[i].z;
Portal->VertexList[i].color=D3DCOLOR_RGBA(rand()%255,rand()%255,rand()%255,240);
Portal->VertexList[i].specular=0;
Portal->VertexList[i].tu=0;
Portal->VertexList[i].tv=0;
}
Portal->Indices[0]=0;
Portal->Indices[1]=1;
Portal->Indices[2]=3;
Portal->Indices[3]=3;
Portal->Indices[4]=1;
Portal->Indices[5]=2;
Portal->Next=NULL;
Portal->Prev=NULL;
Portal->NumberOfLeafs=0;
return Portal;
}
That wasn't so bad was it ?PORTAL * ClipPortal(long Node,PORTAL *Portal)
{
PORTAL *PortalList=NULL,*FrontPortalList=NULL,*BackPortalList=NULL,*Iterator=NULL,*FrontSplit=NULL,*BackSplit=NULL;
switch (ClassifyPoly(&PlaneArray[NodeArray[Node].Plane],(POLYGON *)Portal))
{
We will call ClipPortal initially from out BuildPortals function (which
we will look at later) and pass in the InitialPortal we created by
calling 'CalculateInitialPortal'. We will also pass in a value of Zero
because we want the clipping to start for the portal at the Root node of
the BSP Tree. Next we do something you should be very familiar with by
now, we classify the Portal against the Node Plane. Notice above that
because the first part of our PORTAL structure is the same as our
POLYGON structure we can safely cast the PORTAL to type POLYGON to save
have to re write the functions to deal with portals. The Rest of the
function simply is divided into four 'Case' blocks for our switch
statement with instructions on what to do if the Portal is
CP_FRONT,CP_BACK,CP_SPANNING or CP_ONPLANE. We will look at these cases
one at a time starting with CP_FRONT. Also it may help to refer back to
Diagrams K through P while reading the code as this is where we showed
the Portal being clipped Graphically which may help you picture it in
your head. case CP_FRONT:
if (NodeArray[Node].IsLeaf==0)
// Not a Leaf
{
PortalList=ClipPortal(NodeArray[Node].Front,Portal);
return PortalList;
}
else // Is a Leaf
{
Portal->LeafOwnerArray[Portal->NumberOfLeafs]=NodeArray[Node].Front;
Portal->NumberOfLeafs++;
Portal->Next=NULL;
Portal->Prev=NULL;
return Portal;
}
break;
Phew that was nice and easy. Luckily the CP_BACK case is equally as stress free.case CP_BACK:
if (NodeArray[Node].Back!=-1)// another Node exists
{
PortalList=ClipPortal(NodeArray[Node].Back,Portal);
return PortalList;
}
else// were are in solid space
{
DeletePortal ( Portal);
return NULL;
}
break;
So as you can see if a portal (or more likely a portal fragment) end up
in the Front or Back 'case' it is either passed down the front or back
of the tree,deleted or returned having had its Leaf information saved.
It is not very likely that our Initial Portal will ever fall into these
two cases because it is very large and will be spanning several
polygons.The next 'case' (CP_SPANNING) is the code that is responsible
for splitting the Portals into fragments and sending each Fragment down
the tree keeping track of which fragments survive and returning all the
fragments from the function in a Linked List.case CP_SPANNING:
FrontSplit=new PORTAL;
BackSplit=new PORTAL;
ZeroMemory(FrontSplit,sizeof(PORTAL));
ZeroMemory(BackSplit,sizeof(PORTAL));
SplitPortal(Portal,&PlaneArray[NodeArray[Node].Plane],FrontSplit,BackSplit);
DeletePortal (Portal);
You can see above that we create two new Portals 'FrontSplit' and
'BackSplit' and pass them into the 'SplitPortal' function to be
filled.The parent Portal is then deleted.if (NodeArray[Node].IsLeaf==0)//There is another Front NODE
{
FrontPortalList=ClipPortal(NodeArray[Node].Front,FrontSplit);
}
else// Its about to get pushed into a Leaf
{
FrontSplit->LeafOwnerArray[FrontSplit->NumberOfLeafs]=NodeArray[Node].Front;
FrontSplit->NumberOfLeafs++;
FrontSplit->Prev=NULL;
FrontSplit->Next=NULL;
FrontPortalList=FrontSplit;
}
This should look a bit familiar to you by now.The first thing we have
to do is send the Front Split down the Front Tree of the Current Node if
a Front Tree exists.If not then the Front Split has ended up in a Leaf
so we record in the Portal which leaf it has ended up in.The idea is
that we will send the Front Split down the Front Tree and get returned a
Linked List of fragments that Survived the Front Tree. A pointer to
these fragments is returned and stored in 'FrontPortalList'. You can see
that if the Front Split end up in a Leaf then we simply set
'FrontPortalList' to point at the Front Split portal because this is
obviously the only Portal in the front because it will not get clipped
into any more fragments.If however there is Front Tree to send the Front
Split down then 'FrontPortalList' will contain either the Fragments of
the Front Split that survived or NULL if nothing of the Front Split
survived the Front Tree (totally Clipped Away).
if (NodeArray[Node].Back!=-1)
{
BackPortalList=ClipPortal(NodeArray[Node].Back,BackSplit);
}
else // in solid space so delete
{
DeletePortal ( BackSplit);
}
As you can see above, if there is a back tree we get either the Portal
Fragments of the Back Split returned in BackPortalList or NULL if
nothing of the Back Split survived.Else if there is no back tree this
means the Back Split is in solid space and there for is completely
removed (clipped out).if (FrontPortalList!=NULL)
{
Iterator=FrontPortalList;
while (Iterator->Next!=NULL)
{
Iterator=Iterator->Next;
}
if (BackPortalList!=NULL)
{
Iterator->Next=BackPortalList;
BackPortalList->Prev=Iterator;
}
return FrontPortalList;
}//something in the front list
else ////There is nothing in the front list
{
if (BackPortalList!=NULL) return BackPortalList;
return NULL;
}
return NULL;
break;
All we do above is attach the 'BackPointerList' to the end of the
'FrontPortalList'.First we check if there are any fragments in the Front
Portal List. If there is then we find the last fragment in the front
list and if there are fragments in the back list we attach the last
portal in the Front List to the Back list.In other words, instead of the
last portal in the front list haveing its 'Next' pointer set to null we
now set the 'Next' pointer to point at the Back List.We have now merged
the lists in to one large one.Obviously if there are no portals in the
back List then just Front List is returned.
case CP_ONPLANE:
if (NodeArray[Node].IsLeaf!=0) // this is a Leaf Node
{
Portal->LeafOwnerArray[Portal->NumberOfLeafs]=NodeArray[Node].Front;
Portal->NumberOfLeafs++;
Portal->Next=NULL;
Portal->Prev=NULL;
FrontPortalList=Portal;
}
else
{
FrontPortalList=ClipPortal(NodeArray[Node].Front,Portal);
}
if (FrontPortalList==NULL) return NULL;
if (NodeArray[Node].Back==-1) return FrontPortalList;
while (FrontPortalList!=NULL)
{
PORTAL *tempnext=FrontPortalList->Next;
BackPortalList=NULL;
BackPortalList=ClipPortal(NodeArray[Node].Back,FrontPortalList);// fragment this fragment into the back tree
So we start a while loop to step through each Portal (Fragment) in
PortalFrontList.Each time through the loop we store in 'tempnext' a
pointer to the Next Portal in FrontPortalList because the current portal
will be getting sent down the back tree and possibly deleted which
means when the function returns FrontPortalList could be NULL so
'FrontPortalList->Next' would hold complete gibberish and we would
loose a way to step though the list.As you can see above we send the
Current Fragment (pointed to by 'FrontPortalList' down the back tree and
store a pointer to the list of Fragments that have survived in
'BackPortalList'. 'BackPortalList' will be NULL if nothing of the
Current Front List fragment survived the tree.if (BackPortalList!=NULL)
{
Iterator=BackPortalList;
while (Iterator->Next!=NULL)
{
Iterator=Iterator->Next;
}
// attach the last fragment to the first fragment from a previos loop.
Iterator->Next=PortalList;
if (PortalList!=NULL)
{
PortalList->Prev=Iterator;
}
PortalList=BackPortalList; // portal List now points at the current complete list of fragment collected from each loop
}
FrontPortalList=tempnext;
}
return PortalList;
// ALL DONE
break;
Well admittedly that function can fry your brain a little bit first time
through but if you are comfortable with recursion then there is nothing
here very different from any other recursive function. Its good to try
and and do a dry run in your head and following the code imagine the
root a portal might take.Anyway, to give you a much better view of the
function I will now show you it in its complete form.I have colored the
different section to make easier reading. PORTAL * ClipPortal(long Node,PORTAL *Portal)
{
PORTAL *PortalList=NULL,*FrontPortalList=NULL,*BackPortalList=NULL,*Iterator=NULL,*FrontSplit=NULL,*BackSplit=NULL;
switch (ClassifyPoly(&PlaneArray[NodeArray[Node].Plane],(POLYGON *)Portal))
{
case CP_ONPLANE: Send down Both Trees
if (NodeArray[Node].IsLeaf!=0) // this is a Leaf Node
{
// The Front is a Leaf
Portal->LeafOwnerArray[Portal->NumberOfLeafs]=NodeArray[Node].Front;
Portal->NumberOfLeafs++;
Portal->Next=NULL;
Portal->Prev=NULL;
FrontPortalList=Portal;
}
else
{
// Send the Portal Down the Front List and get returned a list of PortalFragments that survived the Front Tree
FrontPortalList=ClipPortal(NodeArray[Node].Front,Portal);
}
// Then send each fragment down the back tree.
if (FrontPortalList==NULL) return NULL;
if (NodeArray[Node].Back==-1) return FrontPortalList;
while (FrontPortalList!=NULL)
{
PORTAL *tempnext=FrontPortalList->Next;
BackPortalList=NULL;
BackPortalList=ClipPortal(NodeArray[Node].Back,FrontPortalList);
if (BackPortalList!=NULL)
{
Iterator=BackPortalList;
while (Iterator->Next!=NULL)
{
Iterator=Iterator->Next;
}
// attach the last fragment to the first fragment from a previos loop.
Iterator->Next=PortalList;
if (PortalList!=NULL)
{
PortalList->Prev=Iterator;
}
PortalList=BackPortalList; // portal List now points at the current complete list of fragment collected from each loop
}
FrontPortalList=tempnext;
}
return PortalList;
break;
case CP_FRONT: // Either send it down the front tree or add it to the portal list because it has come out
//Empty Space
if (NodeArray[Node].IsLeaf==0)
{
PortalList=ClipPortal(NodeArray[Node].Front,Portal);
return PortalList;
}
else // The Front Node is a Empty Leaf so Add it to the Portal List
{
Portal->LeafOwnerArray[Portal->NumberOfLeafs]=NodeArray[Node].Front;
Portal->NumberOfLeafs++;
Portal->Next=NULL;
Portal->Prev=NULL;
return Portal;
}
break;
case CP_BACK:// either asend it downthe back tree or Delete it if no back tree exists
if (NodeArray[Node].Back!=-1)
{
PortalList=ClipPortal(NodeArray[Node].Back,Portal);
return PortalList;
}
else
{
DeletePortal ( Portal);
return NULL;
}
break;
case CP_SPANNING:
FrontSplit=new PORTAL;
BackSplit=new PORTAL;
ZeroMemory(FrontSplit,sizeof(PORTAL));
ZeroMemory(BackSplit,sizeof(PORTAL));
SplitPortal(Portal,&PlaneArray[NodeArray[Node].Plane],FrontSplit,BackSplit);
DeletePortal (Portal);
if (NodeArray[Node].IsLeaf==0)
{
FrontPortalList=ClipPortal(NodeArray[Node].Front,FrontSplit);
}
else// Its about to get pushed into a Leaf
{
FrontSplit->LeafOwnerArray[FrontSplit->NumberOfLeafs]=NodeArray[Node].Front;
FrontSplit->NumberOfLeafs++;
FrontSplit->Prev=NULL;
FrontSplit->Next=NULL;
FrontPortalList=FrontSplit;
}
if (NodeArray[Node].Back!=-1) // the backsplit is in solid space
{
BackPortalList=ClipPortal(NodeArray[Node].Back,BackSplit);
}
else // delete it its in solid space
{
DeletePortal ( BackSplit);
}
if (FrontPortalList!=NULL)// Find the End of the list and attach it to Front Back List
{
Iterator=FrontPortalList;
while (Iterator->Next!=NULL)
{
Iterator=Iterator->Next;
}
if (BackPortalList!=NULL)
{
Iterator->Next=BackPortalList;
BackPortalList->Prev=Iterator;
}
return FrontPortalList;
}// there is something in the front list
else ////There is nothing in the front list
{
if (BackPortalList!=NULL) return BackPortalList;
return NULL;
}
return NULL;
break;
default:
return NULL;
break;
} //end switch
return NULL;
}
Well I have to say that if you have understood everything up until now
then its all gonna get a little bit easier from here on in.In my opinion
'Portal Generation' is the hardest thing to grasp and also rather hard
to explain or even to show on paper for that matter.Well I did mention
earlier when we were looking at the CP_SPANNING case of the above
function that it uses a function called 'SplitPortal' to wrap the
'SplitPolygon' function call and also copy over the extra portal
information over from the parent about to be split into the two child
splits.This information that had to be retained was mainly just what
leafs the Portal the parent had landed in up to that point.I will just
show you the 'SplitPortal' function for completeness.void SplitPortal(PORTAL *Portal,PLANE *Plane,PORTAL *FrontSplit,PORTAL *BackSplit)
{
SplitPolygon((POLYGON *)Portal,Plane,(POLYGON *)FrontSplit,(POLYGON *)BackSplit);
FrontSplit->NumberOfLeafs=Portal->NumberOfLeafs;
BackSplit->NumberOfLeafs=Portal->NumberOfLeafs;
memcpy(FrontSplit->LeafOwnerArray,Portal->LeafOwnerArray,sizeof(long)*Portal->NumberOfLeafs);
memcpy(BackSplit->LeafOwnerArray,Portal->LeafOwnerArray,sizeof(long)*Portal->NumberOfLeafs);
}
void InitPolygons(void)
{
ReserveInitialMemoryForArrays();
PolygonList=NULL;
PolygonList=LoadMWM("demolevel.mwm");
LoadTextures();
BuildBspTree(0,PolygonList);
BuildPortals();
...
...
}
BuildPortals is the main PortalCreation function and the only one that
the main application has to call to generate the portals for the tree.
Its job is to step through every Node in our BSP tree and at each Node
it calls 'CreateInitialPortal' to create the Initial Portal for that
Node and then Calls 'ClipPortal' to send that Portal down the tree.
After the call to 'ClipPortal' , our BuildPortals function will then
have a list of Portal Fragments for the Current Node.It will then Loop
through the portal list and remove any Portals that are not in Two
leafs.This is a simple case of checking the Portals 'NumberOfLeafs' flag
to see that it is equal to Two.struct NODESTACK
{
long Node;
BYTE JumpBackPoint;
};
Everytime we have to traverse the tree we will record the current node
we are using and also were we have come from so that we can jump back
that point.This is basically just simulting what a C++ function call
would do.If you are half way through 'Function 1' and you call 'Function
2' the current Variables and line that is currently being executed in
'Function 1' is saved on the stack.When function two returns the
computer can return to function one at the same place it left off by
popping the information back off the stack so that the execution of
'Function 1' can again resume from where it left off before 'Function 2'
was called. void BuildPortals(void)
{
long stackpointer=0;
NODESTACK *NodeStack=new NODESTACK[NumberOfNodes+1]
int portalindex;
NodeStack[stackpointer].Node=0;// root node
NodeStack[stackpointer].JumpBackPoint=0;
Above is the code that sets up our Stack. The variable 'stackpointer' is
set to zero because this will be the Node that we start off with.You
can see that after we have safely allocated enough memory to hold more
than enough nodes we fill in the current NodeStack position with the
Node we are going to start with (Node 0). JumpBackPoint is set to zero
meaning there is no jump back point for the first node.Once Node 0 is
done it is time to Return from the function because the whole tree will
have been traversed so this is why we just set the jump back point to
zero.START:
PORTAL *InitialPortal=CalculateInitialPortal(NodeStack[stackpointer].Node);
PORTAL *PortalList=ClipPortal(0,InitialPortal);
PORTAL *Iterator=PortalList; // Step through the Portal Listwhile (Iterator!=NULL)
{
if (Iterator->NumberOfLeafs!=2)// not in two leafs so delete
{
PORTAL *temp=Iterator->Next;
RemovePortalFromList(Iterator);
Iterator=temp;
}
else
{
if (CheckDuplicatePortal(Iterator,&portalindex)==true)
{
PORTAL *temp=Iterator->Next;
RemovePortalFromList(Iterator);
Iterator=temp;
}
The 'CheckDuplicatePortal' array returns 'TRUE' if there is a Portal
already in the portal array that is larger then the current one we are
testing.In other words, if it returns true then the portal we are
testing is no good and we want to delete it. Notice that we pass in the
pointer to an INT 'Portalndex'.The contents of this int is modified by
the 'CheckDuplicatePortals' function.If the function returns false it
means we DO want to keep this portal and add it to the main portal
array.'PortalIndex' will hold the index of the entry in the array that
we need to put this portal.If there is not a duplicate portal then
'PortalIndex' will simply hold the next element on the end of the Portal
Array so we will just be adding a new pointer to the end of the array
(PortalIndex will equal NumberOfPortals) however, if there is a
duplicate portal in the array and this new one is bigger then we want to
replace the portal that is already in the array with this one ,so
'PortalIndex' will hold the position where we should place this new
portal in the array to replace the one thats already there. else
{
PortalArray[portalindex]=Iterator;
if (portalindex==NumberOfPortals)
{
for (int a=0;a>Iterator-<NumberOfLeafs;a++)
{
long Index=Iterator->LeafOwnerArray[a];
LeafArray[Index].PortalIndexList[LeafArray[Index].NumberOfPortals]=NumberOfPortals;
LeafArray[Index].NumberOfPortals++;
}
IncreaseNumberOfPortals();
}
Iterator=Iterator->Next;
} // if not a duplicate portal
} // end else
}// END WHILE LOOP
Above concludes the main while loop.If we have made it here this portal
needs to be added.As we have said 'PortalIndex' holds the position in
the master Portal Array where this portal should be added.If
'PortalIndex==NumberOfPortals' however it means we are NOT simply
replacing a portal that is already there but are adding a new one to the
end of the array.We then loop through this portals 'LeafOwnerArray' to
gain access to the Two leafs this portal is in and then record this
portals index number in the array in the Leaf Structures
'PortalIndexList' array and increase the Leafs portal count
'Leaf.NumberOfPortals'. This means we now have in the Portal which leafs
it exists in and we also have in each leaf a reference to this
portal.This will come in handy when we calculate the PVS.Remember a Leaf
could have many portals in it.if (NodeArray[NodeStack[stackpointer].Node].IsLeaf==0)
{
NodeStack[stackpointer+1].Node=NodeArray[NodeStack[stackpointer].Node].Front;
NodeStack[stackpointer+1].JumpBackPoint=1;
stackpointer++ ;
goto START;
}
BACK:
if (NodeArray[NodeStack[stackpointer].Node].Back!=-1)
{
NodeStack[stackpointer+1].Node=NodeArray[NodeStack[stackpointer].Node].Back;
NodeStack[stackpointer+1].JumpBackPoint=2;
stackpointer++ ;
goto START;
};
END:
stackpointer--;// This is like returning from a function
if (stackpointer>-1)
{
if (NodeStack[stackpointer+1].JumpBackPoint==1) goto BACK;
else
if (NodeStack[stackpointer+1].JumpBackPoint==2) goto END;
}
delete [] NodeStack;
}
Hopefully you can see what is happening here.If there is a Front Node
then we put this Front Node in the Next Position in the stack array
(stackpointer+1) and also store in that stack positions 'JumpBackPoint'
variable a Label Code indicating which label we will need to jump back
to continue processing the Current Node.When the we get to the bottom of
the function we simulate a stack by decreasing the Stack pointer so we
are back at the previous node and jump back to either label
BACK:(JumpBackPoint=1) or label END:(JumpBackPoint=2) or we dont jump
anywhere and just return from the BuildPortals function if
(JumpBackPosition=0) which should only be true for the root node.You can
see that if there is a Back node then we do the same but place a value
of '2' in the JumpBackPoint variable. Unfortunately things look a little
uglier when we loose the clean looking Rescursive approach but thats
just something we have to put up with in this case.You can unroll all
your recursive functions this way and will probably get a speed boost by
not using the stack.This is at the expense of some readablility
though.If you read through the above code and follow the program flow
you should see what is happening without any problems, we are simply
restarting the a loop each time by jumping back to the START: label
everytime a new node is reached but are storing in the stack the current
position we are at in the code before the jump.This way, when we have
dealt with the new node will reach the bottom and will decrease the
stackpointer variable so we are back at the Node we were at before the
jump.But we also have to get back to the position in the code we were at
before the jump which is exactly the purpose of the JumpBackPoint
variable. void BuildPortals(void)
{
long stackpointer=0;
NODESTACK *NodeStack=new NODESTACK[NumberOfNodes+1]
int portalindex;
NodeStack[stackpointer].Node=0;// root node
NodeStack[stackpointer].JumpBackPoint=0;
START:
PORTAL *InitialPortal=CalculateInitialPortal(NodeStack[stackpointer].Node);
PORTAL *PortalList=ClipPortal(0,InitialPortal);
PORTAL *Iterator=PortalList; // Step through the Portal Listwhile (Iterator!=NULL)
{
if (Iterator->NumberOfLeafs!=2)// not in two leafs so delete
{
PORTAL *temp=Iterator->Next;
RemovePortalFromList(Iterator);
Iterator=temp;
}
else
{
if (CheckDuplicatePortal(Iterator,&portalindex)==true)
{
PORTAL *temp=Iterator->Next;
RemovePortalFromList(Iterator);
Iterator=temp;
}
else
{
PortalArray[portalindex]=Iterator;
if (portalindex==NumberOfPortals)
{
for (int a=0;a>Iterator-<NumberOfLeafs;a++)
{
long Index=Iterator->LeafOwnerArray[a];
LeafArray[Index].PortalIndexList[LeafArray[Index].NumberOfPortals]=NumberOfPortals;
LeafArray[Index].NumberOfPortals++;
}
IncreaseNumberOfPortals();
}
Iterator=Iterator->Next;
} // if not a duplicate portal
} // end else
}// END WHILE LOOP
if (NodeArray[NodeStack[stackpointer].Node].IsLeaf==0)
{
NodeStack[stackpointer+1].Node=NodeArray[NodeStack[stackpointer].Node].Front;
NodeStack[stackpointer+1].JumpBackPoint=1;
stackpointer++ ;
goto START;
}
BACK:
if (NodeArray[NodeStack[stackpointer].Node].Back!=-1)
{
NodeStack[stackpointer+1].Node=NodeArray[NodeStack[stackpointer].Node].Back;
NodeStack[stackpointer+1].JumpBackPoint=2;
stackpointer++ ;
goto START;
};
END:
stackpointer--;// This is like returning from a function
if (stackpointer>-1)
{
if (NodeStack[stackpointer+1].JumpBackPoint==1) goto BACK;
else
if (NodeStack[stackpointer+1].JumpBackPoint==2) goto END;
}
delete [] NodeStack;
}
void RemovePortalFromList(PORTAL *RemovePortal)
{
PORTAL *temp=RemovePortal;
PORTAL *PrevPortal,*NextPortal;
if(RemovePortal->Prev!=NULL)
{
PrevPortal=RemovePortal->Prev;
if (RemovePortal->Next!=NULL)
{
PrevPortal->Next=RemovePortal->Next;
}
else
{
PrevPortal->Next=NULL;
}
}// if there is a prev
if (RemovePortal->Next!=NULL)
{
NextPortal=RemovePortal->Next;
if (RemovePortal->Prev!=NULL)
{
NextPortal->Prev=RemovePortal->Prev;
}
else
{
NextPortal->Prev=NULL;
}
}
DeletePortal ( temp );
}
Before we move away from Portal Generation we will just have a look at
the last support function called by the 'BuildPortals' function.
Remember that if valid Portal List was returned from 'ClipPortal' we
still had to check that a Portal that connected the same two leafs
did'nt already exist in the master Portal Array.If one did, then the
'CheckDuplicatePortal' functions job was to compare the size of the new
Portal with the one already in the array and discard the smaller one and
keep the larger one.You may want to take a quick flick back to the
'BuildPortals' code just to refresh your self how this function was
used. bool CheckDuplicatePortal (PORTAL * CheckPortal,int *index)
{
long CheckPortalLeaf1=CheckPortal->LeafOwnerArray[0];
long CheckPortalLeaf2=CheckPortal->LeafOwnerArray[1];
First get both the leafs that the Portal exists in (this is stored in
the Portal LeafOwnerArray remember).We will need these leafs to check
all the other portal in the Portal Array and see if any other Portal
exists in the Same two leafs.If so then we have found a duplicate
portal.
long PALeaf1=0;
long PALeaf2=0;
for (long i=0;i<NumberOfPortals;i++)
{
PALeaf1=PortalArray[i]->LeafOwnerArray[0];
PALeaf2=PortalArray[i]->LeafOwnerArray[1];
if ((CheckPortalLeaf1==PALeaf1 && CheckPortalLeaf2==PALeaf2) ||
(CheckPortalLeaf1==PALeaf2 && CheckPortalLeaf2==PALeaf1))
{
Above we loop through each portal in the array and get the two leafs
that the portal exists in.If this Portal exists in the same two leafs as
our 'CheckPortal' then we have found a match and need to compare sizes
as shown below.
D3DXVECTOR3 Max1,Min1,Max2,Min2;
GetPolygonBounds((POLYGON *)CheckPortal,&Min1,&Max1);
GetPolygonBounds((POLYGON *)PortalArray[i],&Min2,&Max2);
float NewSize=D3DXVec3Length(&(Max1-Min1));// Measure the Lengths of the vector to
// see which is bigger
float OldSize=D3DXVec3Length(&(Max2-Min2));
Above we set up some Vectors that will be used to hold a Bounding Box
for the polygon.We then call 'GetPolygonBounds' which is a simple helper
function that just tests each vertex in the polygon and returns a
Minimum Point and a Maximum Point that bounds the Polygon.We call this
function twice to retrieve the Bounding Box for both the Portal that we
are checking ('CheckPortal') and the one that is currently already in
the array.if (fabs(NewSize)>fabs(OldSize))
{
PORTAL *temp=PortalArray[i];
DeletePortal ( temp );
*index=i;
return false;
}
else
{
return true;// This portal is already in the array
}
}
}
As you can see above if the new portal 'CheckPortal' is larger than the
one already in the array then we delete the portal currently in the
array and record the index number of the portal in the array so that it
is returned to the calling function 'BuildPortals' and the calling
function has an index to place the new portal.The function then returns
'false' informing the calling function that the Portal passed in to the
function to be checked is valid and should be added to the array at the
position held in 'index'.*index=NumberOfPortals;
return false;// This portal was not found inthe array
}
Here is the complete code:-bool CheckDuplicatePortal (PORTAL * CheckPortal,int *index)
{
long CheckPortalLeaf1=CheckPortal->LeafOwnerArray[0];
long CheckPortalLeaf2=CheckPortal->LeafOwnerArray[1];
long PALeaf1=0;
long PALeaf2=0;
for (long i=0;i<NumberOfPortals;i++)
{
PALeaf1=PortalArray[i]->LeafOwnerArray[0];
PALeaf2=PortalArray[i]->LeafOwnerArray[1];
if ((CheckPortalLeaf1==PALeaf1 && CheckPortalLeaf2==PALeaf2) ||
(CheckPortalLeaf1==PALeaf2 && CheckPortalLeaf2==PALeaf1))
{
D3DXVECTOR3 Max1,Min1,Max2,Min2;
GetPolygonBounds((POLYGON *)CheckPortal,&Min1,&Max1);
GetPolygonBounds((POLYGON *)PortalArray[i],&Min2,&Max2);
float NewSize=D3DXVec3Length(&(Max1-Min1));// Measure the Lengths of the vector to
// see which is bigger
float OldSize=D3DXVec3Length(&(Max2-Min2));
if (fabs(NewSize)>fabs(OldSize))
{
PORTAL *temp=PortalArray[i];
DeletePortal ( temp );
*index=i;
return false;
}
else
{
return true;// This portal is already in the array
}
}
}
*index=NumberOfPortals;
return false;// This portal was not found inthe array
}
I am not going to cover the code to the 'GetPolygonBounds' function
because it is just a simplified version of the function 'CalculateBox'
function that we wrote earlier to build a BoundingBox for our leafs and
nodes.The only difference is that it only has to build it for one
polygon. You can check out the code anyway if you want.It can be found
in 'Portals.cpp'

| Please note: I am using the terms 'Bottom Side' and 'Top Side' to describe the relation ship of the Portals to the planes in the above diagram a little loosely here purely for the sake of explaining the diagram. In fact the Normals for the Planes can be facing in any direction changing what is considered top and what is considered bottom. This does not matter at all, all we need to know is that one Portal lays on one side and the other Portal lays on the other side.The orientation of the plane is not important. |

![]() | A you can see once again the Source,Destination and Generator Leafs are all marked as visible from the Source Leaf and the Clip Planes (aka Anti-penumbra) are built from the Source portal to the Destination Portal describing exactly how much of the Generator Leaf is visible through the Destination portal as viewed from the Source portal. You can see that out of the two Generator Portals only one is partially with in the Clip Planes.The portal outside the planes is ignored and not processed, but above you can see that the other portal is partially in side.So we clip away the part of the generator portal that is Outside so we are just left with a fragment that is within the penumbra. What happens next is really intersting. |
![]() | Because the Generator portal is partially visible it also means the Leaf on the other side of this portal is visible so we mark it as so by adding it to the source leafs PVS. Next this new Leaf becomes the New Generator Leaf and the Old generator Leaf becomes the new Destination Leaf meaning the old generator portal now becomes the new destination Portal .This means we now build a new Anti-Penumbra from the same source Portal but to the new Destination portal (the old Generator Portal that was clipped) fully describing what area in the new Generator leaf is still visible.What is great about this is as we recursively move from leaf to leaf doing this (making old Generator Portal new Destination portal and rebuilding the anti-penumbra) and clipping each generator portal to the Anti-Penumbra,the Anti-Penumbra gets smaller and smaller with each recur until in the end no portals in the current generator leaf are in side. |
![]() | Here is an exact example of what I mean.You can see that as we go into each new Generator Leaf the Anti-Penumbra get narrower and narrower raising the chances of clipping out all of the Next Generator Leafs portals(Generator Portals).As we enter every Generator leaf that leaf is added to the Source leafs PVS. We now have a PVS for the current Source Leaf.The diagram to the right shows in grey the leafs that could possibly be seen from the Source Leaf. |

void InitPolygons(void)
{
ReserveInitialMemoryForArrays();
PolygonList=NULL;
PolygonList=LoadMWM("demolevel.mwm");
LoadTextures();
BuildBspTree(0,PolygonList);
BuildPortals();
BytesPerSet=(NumberOfLeafs+7)>>3;
PVSData=(BYTE *)malloc (NumberOfLeafs*BytesPerSet);
ZeroMemory(PVSData,NumberOfLeafs*BytesPerSet);
PVSCompressedSize=CalculatePVS();
PVSData=(BYTE *) realloc(PVSData,PVSCompressedSize);
} // END FUNTION
The first thing we do is calculate the initial space needed to hold the
PVS Data for the tree.At this point we have no idea how much the PVS
data will be compressed so we have to allocate enough space for a worst
case senario or no compression.Once we have the PVS we will also have
the total size of it as well so we can then Re Size this array to the
actual amount needed. We need to allocate enough memory initially so
that there is enough memory for every leaf to have 1 bit for every other
leaf in the tree.long CalculatePVS()
{
BYTE * LeafPVS=new BYTE[BytesPerSet];
long PVSMasterWritePointer=0;
First we allocate a temporary buffer 'LeafPVS' that will be used to hold
the uncompressed PVS information for each Leaf.Remember that we
calculated the value of the global variable 'BytesPerSet' prior to the
function call.Once this function has calculated the PVS information for a
Leaf into this buffer,the buffer is copied into the master PVSData
array being compressed as it is copied. The variable
PVSMasterWritePointer will be used to hold the current write position in
the 'PVSData' array. This is a little like a File pointer when reading
or writing to a file.The variable holds the offset into the master
PVSData array that this Leafs visibility information will be copied
to.This starts at zero obviously for the first leaf.As each Leafs PVS
Information is added to the master 'PVSData' array the
PVSMasterWritePointer will keep track of the position that the Next
Leafs information will be written to. This is also the Offset we will
store in the LEAF structures 'PVSIndex' array that we used earlier in
our 'DrawTree' function to offset into the PVSData array at the correct
position for the current leaf the camera was in.for (long Leaf=0;Leaf<NumberOfLeafs;Leaf++)
{
LeafArray[Leaf].PVSIndex=PVSMasterWritePointer;
ZeroMemory(LeafPVS,BytesPerSet);
SetPVSBit(LeafPVS,Leaf);
Above we set up a for next loop that will loop through each leaf in the
LeafArray and calculate the Visibilty information for that leaf.We start
by storing the value of 'PVSMasterWritePointer' into the leaf structure
so that we can offset in the array during Rendering and get to any
Leafs Visibility information.for (long SPI=0;SPI<LeafArray[Leaf].NumberOfPortals;SPI++)
{
PORTAL * SourcePortal=PortalArray[LeafArray[Leaf].PortalIndexList[SPI]];
long TargetLeaf=SourcePortal->LeafOwnerArray[0];
if (TargetLeaf==Leaf)
{
TargetLeaf=SourcePortal->LeafOwnerArray[1];
}
SetPVSBit(LeafPVS,TargetLeaf);
for (long DPI=0;DPI<LeafArray[TargetLeaf].NumberOfPortals;DPI++)
{
PORTAL * TargetPortal=PortalArray[LeafArray[TargetLeaf].PortalIndexList[DPI]];
if (SourcePortal!=TargetPortal &&
ClassifyPoly(&GetPortalPlane(SourcePortal),(POLYGON
*)TargetPortal)!=CP_ONPLANE)
{
RecursePVS(Leaf,SourcePortal,TargetPortal,TargetLeaf,LeafPVS);
} // End for If Source != Dest
} // End for DPI
} // End for SPI
PVSMasterWritePointer+=CompressLeafSet(LeafPVS,PVSMasterWritePointer);
} // End for Leaf
delete [] LeafPVS;
return PVSMasterWritePointer;
} // End Function
long CalculatePVS()
{
BYTE * LeafPVS=new BYTE[BytesPerSet];
long PVSMasterWritePointer=0;
for (long Leaf=0;Leaf<NumberOfLeafs;Leaf++)
{
LeafArray[Leaf].PVSIndex=PVSMasterWritePointer;
ZeroMemory(LeafPVS,BytesPerSet);
SetPVSBit(LeafPVS,Leaf);
for (long SPI=0;SPI<LeafArray[Leaf].NumberOfPortals;SPI++)
{
PORTAL * SourcePortal=PortalArray[LeafArray[Leaf].PortalIndexList[SPI]];
long TargetLeaf=SourcePortal->LeafOwnerArray[0];
if (TargetLeaf==Leaf)
{
TargetLeaf=SourcePortal->LeafOwnerArray[1];
}
SetPVSBit(LeafPVS,TargetLeaf);
for (long DPI=0;DPI<LeafArray[TargetLeaf].NumberOfPortals;DPI++)
{
PORTAL * TargetPortal=PortalArray[LeafArray[TargetLeaf].PortalIndexList[DPI]];
if (SourcePortal!=TargetPortal &&
ClassifyPoly(&GetPortalPlane(SourcePortal),(POLYGON
*)TargetPortal)!=CP_ONPLANE)
{
RecursePVS(Leaf,SourcePortal,TargetPortal,TargetLeaf,LeafPVS);
} // End for If Source != Dest
} // End for DPI
} // End for SPI
PVSMasterWritePointer+=CompressLeafSet(LeafPVS,PVSMasterWritePointer);
} // End for Leaf
delete [] LeafPVS;
return PVSMasterWritePointer;
} // End Function
void SetPVSBit (BYTE *VisArray,long DestLeaf)
{
long ByteToSet=DestLeaf>>3;
BYTE BitToSet=(BYTE)(DestLeaf-(ByteToSet<<3));
VisArray[ByteToSet]|=1<<BitToSet;
}
As you can see it is very small.To find the Correct BYTE to set in the
temporary Visibility Buffer we divide by 8 (>>3). So for example,
if we called this function to mark Leaf 19 as being visible (remember
''VisArray' is a temporary Visibility Buffer for the current Leaf having
its PVS calculated) then 19/8=2 (truncated to 2 because of rounding)
which is correct.We know that Bit 19 is in the 3rd Byte (VisArray[2]
because its zero based).To find the Bit we wish to set in that Byte we
then Multiply the Byte it is in (2) by 8 (<<3) which gives us
2*8=16. We then take this away from the index of the Leaf we wish to set
(DestLeaf) like so 19-16=3.This tells us we need to set the 4th (zero
basd again) bit in the third Byte. We do this by shifting '1' into the
fourth position in the Byte (shifting it by three puts it in position
four because the '1' is already in position 1 before any shifting is
done).We then 'OR' this Bit with the Visibility array so that if the Bit
is not already set it is Set, and if the Bit is already set then then
the Bit still remains Set.
void RecursePVS(long SourceLeaf,PORTAL *SrcPortal,PORTAL *TargetPortal,long TargetLeaf,BYTE *LeafPVS)
{
long GeneratorLeaf=TargetPortal->LeafOwnerArray[0];
if (GeneratorLeaf==TargetLeaf) GeneratorLeaf=TargetPortal->LeafOwnerArray[1];
SetPVSBit(LeafPVS,GeneratorLeaf);
D3DXVECTOR3 SourceLeafCenter=(LeafArray[SourceLeaf].BoundingBox.BoxMax+LeafArray[SourceLeaf].BoundingBox.BoxMin)/2;
D3DXVECTOR3 TargetLeafCenter=(LeafArray[TargetLeaf].BoundingBox.BoxMax+LeafArray[TargetLeaf].BoundingBox.BoxMin)/2;
int SourceLeafLocation=ClassifyPoint(&SourceLeafCenter,&GetPortalPlane(SrcPortal));
int TargetLeafLocation=ClassifyPoint(&TargetLeafCenter,&GetPortalPlane(TargetPortal));
for (long GPI=0;GPI<LeafArray[GeneratorLeaf].NumberOfPortals;GPI++)
{
if
(PortalArray[LeafArray[GeneratorLeaf].PortalIndexList[GPI]]==TargetPortal){delete
GeneratorPortal;delete SourcePortal;continue;}
PORTAL *SourcePortal=new PORTAL;
*SourcePortal=*SrcPortal;
PORTAL *GeneratorPortal=new PORTAL;
*GeneratorPortal=*PortalArray[LeafArray[GeneratorLeaf].PortalIndexList[GPI]];

int GeneratorLocation=ClassifyPoly(&GetPortalPlane(SourcePortal),(POLYGON*) GeneratorPortal);
if (GeneratorLocation==CP_ONPLANE ||
GeneratorLocation==SourceLeafLocation) {delete GeneratorPortal;delete
SourcePortal;continue;}
GeneratorLocation=ClassifyPoly(&GetPortalPlane(TargetPortal),(POLYGON*) GeneratorPortal);
if (GeneratorLocation==CP_ONPLANE ||
GeneratorLocation==TargetLeafLocation) {delete GeneratorPortal;delete
SourcePortal;continue;}
If we have made it here then we have a Generator portal that could
Possibly be seen so we have to test it against the Anti-Penumbra planes
to find out.GeneratorPortal=ClipToAntiPenumbra(SourcePortal,TargetPortal,GeneratorPortal);
if (GeneratorPortal==NULL)
{
if (SourcePortal) delete SourcePortal;continue;
continue;
}
Above we send into the 'ClipToAntiPenumbra' function the Source,Target
and current Generator portal being tested.This function Creates the Clip
Planes for the Penumbra and Clips the Generator portal to them
returning the result.If the function returns 'NULL' then the Generator
Portal was completely clipped away which means it was outside the
Anti-Penumbra and therefore we dont have to recurse any further for this
Portal because it is not visible.In this case we simply delete the copy
of the Source Portal that we made and move onto the next iteration of
the loop where we will test the next Generator portal.SourcePortal= ClipToAntiPenumbra(GeneratorPortal,TargetPortal,SourcePortal);
if (SourcePortal==NULL)
{
if (GeneratorPortal) delete GeneratorPortal;continue;
}
If a the Generator portal and the Source Portal still exist (have not
been completely clipped away) then we will make this function call
itself BUT this time passing in the Current Generator Portal and
Generator Leaf as theTarget Portal and Target Leaf respectively.This
means the next time round in the recur a New Generator Leaf on the
opposing side of the old Generator Portal (the new target portal) will
be added to the visibility buffer and the Anti-Penumbra will be built
between the source Portal and the new Target portal (old Generator
portal).RecursePVS(SourceLeaf,SourcePortal,GeneratorPortal,GeneratorLeaf,LeafPVS);
delete GeneratorPortal;
delete SourcePortal;
}
}
That was not as bad as it could have been.Read through again until you
understand the recursive process involved.The key to understanding it is
in understanding how the next time the function calls itself it uses
the Old Generator portal as the new target portal to find a new
Generator Leaf on the opposing side of this new Target Portal.Remember
that each new Generator Leaf found is considered to be visible and has
its Bit set in the Visibility buffer.void RecursePVS(long SourceLeaf,PORTAL *SrcPortal,PORTAL *TargetPortal,long TargetLeaf,BYTE *LeafPVS)
{
long GeneratorLeaf=TargetPortal->LeafOwnerArray[0];
if (GeneratorLeaf==TargetLeaf) GeneratorLeaf=TargetPortal->LeafOwnerArray[1];
SetPVSBit(LeafPVS,GeneratorLeaf);
D3DXVECTOR3 SourceLeafCenter=(LeafArray[SourceLeaf].BoundingBox.BoxMax+LeafArray[SourceLeaf].BoundingBox.BoxMin)/2;
D3DXVECTOR3 TargetLeafCenter=(LeafArray[TargetLeaf].BoundingBox.BoxMax+LeafArray[TargetLeaf].BoundingBox.BoxMin)/2;
int SourceLeafLocation=ClassifyPoint(&SourceLeafCenter,&GetPortalPlane(SrcPortal));
int TargetLeafLocation=ClassifyPoint(&TargetLeafCenter,&GetPortalPlane(TargetPortal));
for (long GPI=0;GPI<LeafArray[GeneratorLeaf].NumberOfPortals;GPI++)
{
if (PortalArray[LeafArray[GeneratorLeaf].PortalIndexList[GPI]]==TargetPortal){continue;}
PORTAL *SourcePortal=new PORTAL;
*SourcePortal=*SrcPortal;
PORTAL *GeneratorPortal=new PORTAL;
*GeneratorPortal=*PortalArray[LeafArray[GeneratorLeaf].PortalIndexList[GPI]];
int GeneratorLocation=ClassifyPoly(&GetPortalPlane(SourcePortal),(POLYGON*) GeneratorPortal);
if (GeneratorLocation==CP_ONPLANE ||
GeneratorLocation==SourceLeafLocation) {delete GeneratorPortal;delete
SourcePortal;continue;}
GeneratorLocation=ClassifyPoly(&GetPortalPlane(TargetPortal),(POLYGON*) GeneratorPortal);
if (GeneratorLocation==CP_ONPLANE ||
GeneratorLocation==TargetLeafLocation) {delete GeneratorPortal;delete
SourcePortal;continue;}
GeneratorPortal=ClipToAntiPenumbra(SourcePortal,TargetPortal,GeneratorPortal);
if (GeneratorPortal==NULL)
{
if (SourcePortal) delete SourcePortal;continue;
continue;
}
SourcePortal= ClipToAntiPenumbra(GeneratorPortal,TargetPortal,SourcePortal);
if (SourcePortal==NULL)
{
if (GeneratorPortal) delete GeneratorPortal;continue;
}
RecursePVS(SourceLeaf,SourcePortal,GeneratorPortal,GeneratorLeaf,LeafPVS);
delete GeneratorPortal;
delete SourcePortal;
}
}

struct CLIPPLANES
{
WORD NumberOfPlanes;
PLANE *Planes;
};
No real surprises here then. The CLIPPLANES structure contains a pointer
to an array of Clip Planes and also a Count of how many planes are in
the array.
PORTAL * ClipToAntiPenumbra(PORTAL *SourcePortal,PORTAL *TargetPortal,PORTAL *GeneratorPortal)
{
D3DXVECTOR3 EdgeVector1,EdgeVector2,Normal;
int PortalLocation;
PORTAL *FrontSplit,*BackSplit,*TempSource,*TempTarget;
CLIPPLANES ClipPlanes; // Create a ClipPlane set
ClipPlanes.NumberOfPlanes=0;
ClipPlanes.Planes=new PLANE [SourcePortal->NumberOfVertices*TargetPortal->NumberOfVertices*2];
PLANE TempPlane;
int NextVertex=0;
We start of by delaring some local variables that we will be using
shortly including a CLIPPLANE structure called 'ClipPlanes'.We set its
count to zero because we have not added any Planes to the list yet and
also allocate enough memory in the CLIPPLANE structure to hold the
maximum number of Clip Planes thats could possibly be created.In
practice much fewer Clip Planes will be added to this list because
remember that we are only going to accept clip planes that divide the
Source and Target portals and many will not so will never be added. But
to be on the safe side we reserve enough memory so that it could contain
EVERY plane that was creates from the Source to Target and from the
Target to Source.We multiply this by two because remember that we are
also going to generate a set of clip planes from the Target to the
Source Portal as well as from the Source to the Target Portal.for (int loop=0;loop<2;loop++)
{
if (loop==0)
{
TempSource=SourcePortal;
TempTarget=TargetPortal;
}
else
{
TempSource=TargetPortal;
TempTarget=SourcePortal;
}
We now how our Temporary Source and Target portal pointers set up to
point to the correct Portal depending on the current iterartion of the
loop.Now its time to create the planes from each vertex in the Source
portal to each Edge in the Destination portal.First we set up a for next
loop to loop through each vertex in the Source (temporary Source that
is).This will be the First point needed to construct the plane.What we
do have to check for though is that the Source Portals Vertex is not ON
the Target Portals planes .This can happen if say for example 2 portals
meet in an 'L' shaped configuration.In this situation the Clip Plane can
not be built so we simply skip the vertex and move on to the next one.
for (int SV=0;SV<TempSource->NumberOfVertices;SV++)
{
PortalLocation=ClassifyPoint((D3DXVECTOR3 *)&TempSource->VertexList[SV],&GetPortalPlane(TempTarget));
if (PortalLocation==CP_ONPLANE) continue; // Skip It. Because it;s 'L' shaped
Providing that the Source Vertex is not ON_PLANE with the Target Portal
we now have to loop through each edge in the Target portal.Once again we
have to do a little bit of conditional logic at the top of this inner
loop because if we just loop through each vertex in the Target Portal
and use ''Vertex' and ''Vertex+1' as our edge we will get to the point
where we are at the Last vertex in the Target Portal so ''Vertex+1' does
not exist.All we have to do is check if we are at the last vertex in
the edge and if so wrap back around so that the last edge uses ''Vertex'
and 'FirstVertex' like so:-for (int TP=0;TP<TempTarget->NumberOfVertices;TP++)
{
if (TP==TempTarget->NumberOfVertices-1)
{
NextVertex=0;
}
else
{
NextVertex=TP+1 ;
}
You can see that TP will hold the Current Vertex (which will be used as
the first vertex in each edge) and 'NextVertex' will hold the
secondVertex in the edge.This will just be set to 'TP+1' unless 'TP' is
the Last vertex in the Target portal in which case we will set
'NextVertex' to Zero( The First Vertex in the Target Portal).EdgeVector1=*((D3DXVECTOR3 *)&TempSource->VertexList[SV])-*((D3DXVECTOR3 *)&TempTarget->VertexList[TP]);
EdgeVector2=*((D3DXVECTOR3 *)&TempTarget->VertexList[NextVertex])-*((D3DXVECTOR3 *)&TempTarget->VertexList[TP]);
D3DXVec3Cross(&Normal,&EdgeVector1,&EdgeVector2);// create normal
D3DXVec3Normalize(&TempPlane.Normal,&Normal); // make it a unit normal
TempPlane.PointOnPlane=*(D3DXVECTOR3 *)&TempSource->VertexList[SV];//Any vertex will do
if(ClassifyPoly(&TempPlane,(POLYGON*)TempSource)== CP_FRONT)
{
if(ClassifyPoly(&TempPlane,(POLYGON*)TempTarget)== CP_BACK)
{
ClipPlanes.Planes[ClipPlanes.NumberOfPlanes] = TempPlane;
ClipPlanes.NumberOfPlanes++;
//Save clip plane
}
}
else
if(ClassifyPoly(&TempPlane,(POLYGON *)TempSource) == CP_BACK)
{
if(ClassifyPoly(&TempPlane,(POLYGON*)TempTarget) == CP_FRONT)
{
ClipPlanes.Planes[ClipPlanes.NumberOfPlanes] = TempPlane;
ClipPlanes.NumberOfPlanes++;
//Save clip plane
}
}
} // end for tp
} // end for sp
} // END FOR LOOP
At this point we have exited all the loops we started and 'ClipList'
hold the Number of Clip Planes in the Anti-Penumbra and also contains a
pointer to those Clip Planes.At last we have our Anti-penumbra built.All
we have to do now is see if the Generator portal is Inside the
Anti-penumbra or not. We do this by looping though each Clip Plane and
clip each generator Portal to the Clip Plane..Just to jog your memory I
will show you Diagram W) again so you can see how the clipping works:-
for(int I = 0; I < ClipPlanes.NumberOfPlanes; i++)
{
PortalLocation= ClassifyPoly(&ClipPlanes.Planes[i],(POLYGON *)GeneratorPortal);
int SourcePortalLocation=ClassifyPoly(&ClipPlanes.Planes[i],(POLYGON *)SourcePortal);
if(PortalLocation == SourcePortalLocation || PortalLocation==CP_ONPLANE)// its entirley out side the anitpenumbra so loose it
{
delete Generator portal;
delete ClipPlanes.Planes;
return NULL;
}
if((PortalLocation == CP_BACK && SourcePortalLocation==CP_FRONT)
|| (PortalLocation == CP_FRONT &&
SourcePortalLocation==CP_BACK))// inside Anti Penumbra
{
//keep it
continue;
}
if(PortalLocation == CP_SPANNING)
{
//Clip the portal
FrontSplit=new PORTAL;
BackSplit=new PORTAL;
ZeroMemory(FrontSplit,sizeof(PORTAL));
ZeroMemory(BackSplit,sizeof(PORTAL));
SplitPortal(GeneratorPortal,&ClipPlanes.Planes[i],FrontSplit,BackSplit);
if (SourcePortalLocation==CP_FRONT)
{
delete FrontSplit;
delete GeneratorPortal;
GeneratorPortal=BackSplit;
}
else
if (SourcePortalLocation==CP_BACK)
{
delete BackSplit;
delete GeneratorPortal;
GeneratorPortal=FrontSplit;
}
}
}
delete ClipPlanes.Planes;
return GeneratorPortal;
} // end function
WOW, we are nearly all done with this PVS stuff. We have certainly done
all the hard bits all that left to do now is compress each Visibility
Buffer and add it to the Master PVSData array.Before we do that however
lets see the 'ClipToAntiPenumbra' function in its entiretyPORTAL * ClipToAntiPenumbra(PORTAL *SourcePortal,PORTAL *TargetPortal,PORTAL *GeneratorPortal)
{
D3DXVECTOR3 EdgeVector1,EdgeVector2,Normal;
int PortalLocation;
PORTAL *FrontSplit,*BackSplit,*TempSource,*TempTarget;
CLIPPLANES ClipPlanes; // Create a ClipPlane set
ClipPlanes.NumberOfPlanes=0;
ClipPlanes.Planes=new PLANE [SourcePortal->NumberOfVertices*TargetPortal->NumberOfVertices*2];
PLANE TempPlane;
int NextVertex=0;
for (int loop=0;loop<2;loop++)
{
if (loop==0)
{
TempSource=SourcePortal;
TempTarget=TargetPortal;
}
else
{
TempSource=TargetPortal;
TempTarget=SourcePortal;
}
for (int SV=0;SV<TempSource->NumberOfVertices;SV++)
{
PortalLocation=ClassifyPoint((D3DXVECTOR3 *)&TempSource->VertexList[SV],&GetPortalPlane(TempTarget));
if (PortalLocation==CP_ONPLANE) continue;
for (int TP=0;TP<TempTarget->NumberOfVertices;TP++)
{
if (TP==TempTarget->NumberOfVertices-1)
{
NextVertex=0;
}
else
{
NextVertex=TP+1 ;
}
EdgeVector1=*((D3DXVECTOR3 *)&TempSource->VertexList[SV])-*((D3DXVECTOR3 *)&TempTarget->VertexList[TP]);
EdgeVector2=*((D3DXVECTOR3 *)&TempTarget->VertexList[NextVertex])-*((D3DXVECTOR3 *)&TempTarget->VertexList[TP]);
D3DXVec3Cross(&Normal,&EdgeVector1,&EdgeVector2);// create normal
D3DXVec3Normalize(&TempPlane.Normal,&Normal); // make it a unit normal
TempPlane.PointOnPlane=*(D3DXVECTOR3 *)&TempSource->VertexList[SV];//Any vertex will do
if(ClassifyPoly(&TempPlane,(POLYGON*)TempSource)== CP_FRONT)
{
if(ClassifyPoly(&TempPlane,(POLYGON*)TempTarget)== CP_BACK)
{
ClipPlanes.Planes[ClipPlanes.NumberOfPlanes] = TempPlane;
ClipPlanes.NumberOfPlanes++;
//Save clip plane
}
}
else
if(ClassifyPoly(&TempPlane,(POLYGON *)TempSource) == CP_BACK)
{
if(ClassifyPoly(&TempPlane,(POLYGON*)TempTarget) == CP_FRONT)
{
ClipPlanes.Planes[ClipPlanes.NumberOfPlanes] = TempPlane;
ClipPlanes.NumberOfPlanes++;
//Save clip plane
}
}
} // end for tp
} // end for sp
} // END FOR LOOP
// We now have our PLane List so lets clip the Generator Portal against each one.
for(int I = 0; I < ClipPlanes.NumberOfPlanes; i++)
{
PortalLocation= ClassifyPoly(&ClipPlanes.Planes[i],(POLYGON *)GeneratorPortal);
int SourcePortalLocation=ClassifyPoly(&ClipPlanes.Planes[i],(POLYGON *)SourcePortal);
if(PortalLocation == SourcePortalLocation || PortalLocation==CP_ONPLANE)// its entirley out side the anitpenumbra so loose it
{
delete Generator portal;
delete ClipPlanes.Planes;
return NULL;
}
if((PortalLocation == CP_BACK && SourcePortalLocation==CP_FRONT)
|| (PortalLocation == CP_FRONT &&
SourcePortalLocation==CP_BACK))// inside Anti Penumbra
{
//keep it
continue;
}
if(PortalLocation == CP_SPANNING)
{
//Clip the portal
FrontSplit=new PORTAL;
BackSplit=new PORTAL;
ZeroMemory(FrontSplit,sizeof(PORTAL));
ZeroMemory(BackSplit,sizeof(PORTAL));
SplitPortal(GeneratorPortal,&ClipPlanes.Planes[i],FrontSplit,BackSplit);
if (SourcePortalLocation==CP_FRONT)
{
delete FrontSplit;
delete GeneratorPortal;
GeneratorPortal=BackSplit;
}
else
if (SourcePortalLocation==CP_BACK)
{
delete BackSplit;
delete GeneratorPortal;
GeneratorPortal=FrontSplit;
}
}
}
delete ClipPlanes.Planes;
return GeneratorPortal;
} // end function
Ummm, there is no doubt about it, that function can look like one ugly
son of a bitch on first viewing, but if you break it down into its two
parts 'Creating The Planes' and 'Clipping the Generator portal to the
Planes' it starts looking a bit more friendly.PVSMasterWritePointer+=CompressLeafSet(LeafPVS,PVSMasterWritePointer);
long CompressLeafSet (BYTE * VisArray, long WritePos)
{
int j;
int rep;
BYTE *dest=&PVSData[WritePos];
BYTE *dest_p;
dest_p = dest;
for (j=0 ; j<BytesPerSet ; j++)
{
*dest_p++ = VisArray[j];
if (VisArray[j])
continue;
rep = 1;
for ( j++; j<BytesPerSet ; j++)
if (VisArray[j] || rep == 255)
break;
else
rep++;
*dest_p++ = rep;
j--;
}
return dest_p - dest;
}
It is sometimes hard to believe that such a small function could save so much memory.
if (pvs&mask)
{
if (LeafInFrustum(currentleaf)==true)
{
...
Draw the Leaf
...
During the RenderTree function we loop through each Bit in the Camera
Leafs PVS (the Leaf the Camera is in) and if it has its bit set to '1'
(if pvs&mask) then the Leaf is in the PVS set and is potentially
visible from the Camera Leaf. HOWEVER, instead of just rendering the
Leaf we do a second check by calling the 'LeafInFrustum' function.This
function takes a Leaf index as a parameter and returns TRUE if any part
of the Leafs Bounding Box is within the View Frustum(Cameras View).
Therefore we don't have to render any leafs which make this Function
return false. In the above diagram, this function would return false for
all leave except Leaf 1 as this is the only Leaf that can be partially
seen from the Camera.These tests are actually remarkably Quick because
our Leafs Bounding Boxes are whats known as being 'Axis Aligned Bounding
Boxes' which means they are aligned to the Major Axis of the 3D
world.In other Words the Bounding Box Z Maximum reaches in the same
direction as the Worlds Positive Z Axis and the Box's X Maximum is in
the direction of the Worlds Positive X axis etc etc.This provides
advantages because Culling Axis Aligned Bounding Boxes (AABB's) from the
frustum can be done very quickly
D3DXMatrixPerspectiveFovLH(&proj_m, 1.0f,0.77777f,0.3f, 500.0f );
struct PLANE2
{
float Distance;
D3DXVECTOR3 Normal;
};
We also define at the start of our program an Global array of six PLANE2
structures that will be used to hold the frustum Planes each frame.
PLANE2 FrustumPlanes[6];void renderframe(void)
{
UpdateViewPos();
ExtractFrustumPlanes(FrustumPlanes);
lpDevice->Clear(0,NULL,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(0,0,0),1.0f,0);
lpDevice->BeginScene();
RenderTree(CameraPosition);
lpDevice->EndScene();
}
This all makes sense but before I get a load of emails telling me how my
Demo does not do it this way round let me just say that this is because
of some trickery I had to pull off for the Top Down View mode so that
when you look down from above the Frustum Planes are NOT where the
camera is (because it is up in the air looking down) but instead the
Frustum Planes remain in the same position as they were when you were in
First Person View.This allows you to View the Clipping process from
above.In case you are interested , i did this by using two view
matrices.The First one is where the Person is on the ground (or the
camera if you are in First Person Mode) and the second is the acutal
camera position.In the Demo i Build the Player location View Matrix
First and use THIS (not the camera) matrix to extract the planes and
clip the tree.Then after the tree has been culled i set the View matrix
to the actual Camera Position for rendering. Any way all of this is
besides the point, in a first person game you will use a render loop
like the one i have used above.You can view the source code to see the
changes i had to make to the Top Down mode.|
A Word Of Thanks:- I would like to say a very big thankyou to Klaus Hartmann for bringing the following code to my attention. Klaus is keen to point out that he did not invent this and it is a guy called 'Gil Gribb' who deserves the credit.However, it was Klaus that was good enough to explain Not Only the following code but also the technique to Cull AABB's to the View Frustum and the code to the following two functions is based very tightly on code that he gave to me.Thanks again Klaus. |
void ExtractFrustumPlanes(PLANE2 *Planes)
{
D3DXMATRIX ViewMatrix,ProjMatrix,ViewProj;
lpDevice->GetTransform(D3DTS_PROJECTION,&ProjMatrix);
lpDevice->GetTransform(D3DTS_VIEW,&ViewMatrix);
D3DXMatrixMultiply(&ViewProj,&ViewMatrix,&ProjMatrix); //Combine Them
// Left clipping plane
Planes[0].Normal.x = -(ViewProj._14 + ViewProj._11);
Planes[0].Normal.y = -(ViewProj._24 + ViewProj._21);
Planes[0].Normal.z = -(ViewProj._34 + ViewProj._31);
Planes[0].Distance = -(ViewProj._44 + ViewProj._41);
// Right clipping plane
Planes[1].Normal.x = -(ViewProj._14 - ViewProj._11);
Planes[1].Normal.y = -(ViewProj._24 - ViewProj._21);
Planes[1].Normal.z = -(ViewProj._34 - ViewProj._31);
Planes[1].Distance = -(ViewProj._44 - ViewProj._41);
// Top clipping plane
Planes[2].Normal.x = -(ViewProj._14 - ViewProj._12);
Planes[2].Normal.y = -(ViewProj._24 - ViewProj._22);
Planes[2].Normal.z = -(ViewProj._34 - ViewProj._32);
Planes[2].Distance = -(ViewProj._44 - ViewProj._42);
// Bottom clipping plane
Planes[3].Normal.x = -(ViewProj._14 + ViewProj._12);
Planes[3].Normal.y = -(ViewProj._24 + ViewProj._22);
Planes[3].Normal.z = -(ViewProj._34 + ViewProj._32);
Planes[3].Distance = -(ViewProj._44 + ViewProj._42);
// Near clipping plane
Planes[4].Normal.x = -(ViewProj._14 + ViewProj._13);
Planes[4].Normal.y = -(ViewProj._24 + ViewProj._23);
Planes[4].Normal.z = -(ViewProj._34 + ViewProj._33);
Planes[4].Distance = -(ViewProj._44 + ViewProj._43);
// Far clipping plane
Planes[5].Normal.x = -(ViewProj._14 - ViewProj._13);
Planes[5].Normal.y = -(ViewProj._24 - ViewProj._23);
Planes[5].Normal.z = -(ViewProj._34 - ViewProj._33);
Planes[5].Distance = -(ViewProj._44 - ViewProj._43);
// NORMALIZE THE PLANES (Not Really Needed in Our Demo)
for (int i=0;i<6;i++)
{
float denom = 1.0f / D3DXVec3Length(&Planes[i].Normal);// Get magnitude of Vector
Planes[i].Normal.x *= denom;
Planes[i].Normal.y *= denom;
Planes[i].Normal.z *= denom;
Planes[i].Distance *= denom;
}
}// End Function Extract Clip Planes

|
Lets imagine that first of all we want to check the Left (L) Clip plane
of the Frustum and the Camera is facing due North.In this case the Left
Clip planes Normal would be facing in the Negative X Direction which
means that we will use the AABB X Maximum Point as our Near Point X
Component.The same is true for the the Z component of the Plane
Normal.This too would be a Negative component meaning we would use the
AABB Maximum Z Component as the Near points Z Component (Forget about Y
for now because we are looking top down and the plane has no Y
orientation). In this case then the Near Point (corner point of the AABB) used will be the Top Right of the Box as indicated by the Green square.Look at this Corner Point on 'BOX A' and remembering that Planes are Infinite you should be able to see that if this 'Near Point' is In Front of the Left Plane the the WHOLE AABB must be also. When testing the Right Plane a Different 'Near' Point is Used because the Plane Normals X Component now faces Positive which means we use the AABB's X Minimum component and the Near Point X Component.Z is still facing the same way however (Negative) so we still use the AABB's Max Z component.This gives us the Corner indicated by the RED box's in the Diagram. Look at 'Box B' you should also be able to see that in this case if the RED corner is Infront of the Right Plane then the entire AABB must be also. |
bool LeafInFrustum (long Leaf)
{
D3DXVECTOR3 bMax=LeafArray[Leaf].BoundingBox.BoxMax;
D3DXVECTOR3 bMin=LeafArray[Leaf].BoundingBox.BoxMin;
D3DXVECTOR3 NearPoint,FarPoint;
PLANE2 *Plane=FrustumPlanes;
for (int i=0;i<6;i++)
{
if (Plane->Normal.x > 0.0f)
{
if (Plane->Normal.y > 0.0f)
{
if (Plane->Normal.z > 0.0f)
{
NearPoint.x = bMin.x; NearPoint.y = bMin.y; NearPoint.z = bMin.z;
}
else
{
NearPoint.x = bMin.x; NearPoint.y = bMin.y; NearPoint.z = bMax.z;
}
}
else
{
if (Plane->Normal.z > 0.0f)
{
NearPoint.x = bMin.x; NearPoint.y = bMax.y; NearPoint.z = bMin.z;
}
else
{
NearPoint.x = bMin.x; NearPoint.y = bMax.y; NearPoint.z = bMax.z;
}
}
}
else
{
if (Plane->Normal.y > 0.0f)
{
(Plane->Normal.z > 0.0f)
{
NearPoint.x = bMax.x; NearPoint.y = bMin.y; NearPoint.z = bMin.z;
}
else
{
NearPoint.x = bMax.x; NearPoint.y = bMin.y; NearPoint.z = bMax.z;
}
}
else
{
if (Plane->Normal.z > 0.0f)
{
NearPoint.x = bMax.x; NearPoint.y = bMax.y; NearPoint.z = bMin.z;
}
else
{
NearPoint.x = bMax.x; NearPoint.y = bMax.y; NearPoint.z = bMax.z;
}
}
}
// near extreme point is outside, and thus
// the AABB is Totally outside the polyhedron
if (D3DXVec3Dot(&Plane->Normal,&NearPoint)+Plane->Distance>0) return false;
Plane++;
}
return true;
}
Thats it!!!. Although we have used the above function to test the Leafs
of our Bounding Box against the Frustum , the above technique works with
any AABB and so can readily be used in any other applications that need
to perform Frustum Culling of AABB's. Although Frustum Culling and
Extracting the Frustum Planes are NOT really BSP exclusive subjects and
are generic to all 3D Applications i felt by including it in this
tutoria it would make the Tutorial more complete and you well on your
way to writing a lightning fast 3D engine.| BSP Compiler Structures | Loader and Renderer Structures |
|
struct NODE { unsigned char IsLeaf; unsigned long Plane; unsigned long Front; signed long Back; BOUNDINGBOX BoundingBox; }; |
struct NODE { unsigned char IsLeaf; unsigned long Plane; unsigned long Front; signed long Back; }; |
| You can see that when the Compiler saves the Node Array it does not save the Bounding Box.This means our loader and renderer application can leave this field out of its Node Structure | |
|
struct LEAF{ long StartPolygon; long EndPolygon; long PortalIndexList[50]; long NumberOfPortals; long PVSIndex; BOUNDINGBOX BoundingBox; }; |
struct LEAF{ long StartPolygon; long EndPolygon; long PVSIndex; BOUNDINGBOX BoundingBox; }; |
| When we save the Leaf array from the compiler we do not bother to save the Portal Information because this will not be used by the Loader and Renderer.The portals were only needed to calculate the PVS.We will not save the Portal Array at all. | |
|
struct POLYGON { D3DLVERTEX *VertexList; D3DXVECTOR3 Normal; WORD NumberOfVertices; WORD NumberOfIndices; WORD *Indices; POLYGON * Next; bool BeenUsedAsSplitter; long TextureIndex; }; |
struct POLYGON { D3DLVERTEX *VertexList; D3DXVECTOR3 Normal; WORD NumberOfVertices; WORD NumberOfIndices; WORD *Indices; POLYGON * Next; long TextureIndex; }; |
| Not so much of a saving here.We obviously do not need the 'BeenUsedAsSplitter' variable any more as this was only used for compilation.Obviously we also do not save off the 'Next' pointer as this would be meaningless when loaded back in (because it points to an actual memory address) BUT our but we still have to have this field in out 'Loader and Renderer' application because if you remember, the 'Next' pointer was also used for Texture Batching during rendering.We will still want this functionailty when in our Loader and Renderer Application. | |
void SaveBSPTree(char *filename)
{
FILE *stream;
long a;
stream = fopen(filename, "w+b");
NumberOfNodes++;
fwrite(&NumberOfNodes,sizeof(long),1,stream);
NODE *n=NodeArray;
for (a=0;a<NumberOfNodes;a++)
{
fwrite(&n->IsLeaf,sizeof (unsigned char),1,stream);
fwrite(&n->Plane, sizeof (unsigned long),1,stream);
fwrite(&n->Front, sizeof (unsigned long),1,stream);
fwrite(&n->Back , sizeof (signed long),1,stream);
n++;
}
// Write the Plane Array
fwrite(&NumberOfPlanes,sizeof(long),1,stream);
fwrite(PlaneArray,sizeof(PLANE),NumberOfPlanes,stream);
// Write Leaf Array.This also has some reduntant Run Time fields so we shall remove
// these when writing.
fwrite(&NumberOfLeafs,sizeof(long),1,stream);
LEAF *l=LeafArray;
for (a=0;a&lyNumberOfLeafs;a++)
{
fwrite(&l->StartPolygon,sizeof(long),1,stream);
fwrite(&l->EndPolygon,sizeof(long),1,stream);
fwrite(&l->PVSIndex,sizeof(long),1,stream);
fwrite(&l->BoundingBox,sizeof(BOUNDINGBOX),1,stream);
l++;
}
// WritePolygonArray.There are fields in this structure that were used for sompiling and are
// not needed at runtime."BeenUsedAsSplitter' is not needed and neither is the 'Next' Pointer.
fwrite(&NumberOfPolygons,sizeof(long),1,stream);
POLYGON *p=PolygonArray;
for (a=0;a<NumberOfPolygons;a++)
{
fwrite(&p->NumberOfVertices,sizeof(WORD),1,stream);
fwrite(p->VertexList,sizeof(D3DLVERTEX),p->NumberOfVertices,stream);
fwrite(&p->NumberOfIndices,sizeof(WORD),1,stream);
fwrite(p->Indices,sizeof(WORD),p->NumberOfIndices,stream);
fwrite(&p->Normal,sizeof(D3DXVECTOR3),1,stream);
fwrite(&p->TextureIndex,sizeof(long),1,stream);
p++;
}
// Now all we have to do is write the PVS Data itself
fwrite(&PVSCompressedSize,sizeof(long),1,stream);
fwrite(PVSData,sizeof(BYTE),PVSCompressedSize,stream);
// All done
fclose (stream);
}
As you can see we just write the Arrays out in the following Order
.1)NodeArray 2)PlaneArray 3)Leaf Array 4)PolygonArray 5)PVSData. It is a
shame actually that we have to miss out certain fields or other wise we
could have written this function in just a few lines of code.Look at
how the PLANE's array is written.We just throw the Whole array into the
file in one go because we need it all.void LoadBSPTree(char *filename)
{
FILE *stream;
long a;
stream = fopen(filename, "rb");
fread(&NumberOfNodes,sizeof(long),1,stream);
NodeArray= (NODE *) malloc (NumberOfNodes*sizeof(NODE));
NODE *n=NodeArray;
for (a=0;a<NumberOfNodes;a++)
{
fread(&n->IsLeaf,sizeof (unsigned char),1,stream);
fread(&n>Plane, sizeof (unsigned long),1,stream);
fread(&n>Front, sizeof (unsigned long),1,stream);
fread(&n>Back , sizeof (signed long),1,stream);
n++;
}
// Write the Plane Array
fread(&NumberOfPlanes,sizeof(long),1,stream);
PlaneArray =(PLANE *) malloc (NumberOfPlanes*sizeof(PLANE));
fread(PlaneArray,sizeof(PLANE),NumberOfPlanes,stream);
// Write Leaf Array.This also has some reduntant Run Time fields so we shall remove
// these when writing.
fread(&NumberOfLeafs,sizeof(long),1,stream);
LeafArray =(LEAF *)malloc (NumberOfLeafs*sizeof(LEAF));
fread (LeafArray,sizeof(LEAF),NumberOfLeafs,stream);
// WritePolygonArray.
fread(&NumberOfPolygons,sizeof(long),1,stream);
PolygonArray=(POLYGON *)malloc (NumberOfPolygons*sizeof(POLYGON));
POLYGON *p=PolygonArray;
for (a=0;a<NumberOfPolygons;a++)
{
fread(&p>NumberOfVertices,sizeof(WORD),1,stream);
p>VertexList=new D3DLVERTEX[p>NumberOfVertices];
fread(p>VertexList,sizeof(D3DLVERTEX),p>NumberOfVertices,stream);
fread(&p>NumberOfIndices,sizeof(WORD),1,stream);
p>Indices=new WORD [p>NumberOfIndices];
fread(p>Indices,sizeof(WORD),p>NumberOfIndices,stream);
fread(&p>Normal,sizeof(D3DXVECTOR3),1,stream);
fread(&p>TextureIndex,sizeof(DWORD),1,stream);
p++;
}
// Now all we have to do is write the PVS Data itself
fread(&PVSCompressedSize,sizeof(long),1,stream);
PVSData =(BYTE *)malloc (PVSCompressedSize*sizeof(BYTE));
fread(PVSData,sizeof(BYTE),PVSCompressedSize,stream);
// All done
fclose (stream);
}
Piece of cake hey?
